1 /**
2  * ae.ui.timer.thread.timer
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.ui.timer.thread.timer;
15 
16 import core.thread;
17 import core.sync.semaphore;
18 
19 public import ae.ui.timer.timer;
20 import ae.ui.app.application;
21 import ae.sys.timing;
22 
23 private alias ae.sys.timing.Timer SysTimer;
24 private alias ae.ui.timer.timer.Timer Timer;
25 
26 /// A simple thread-based `Timer` implementation.
27 final class ThreadTimer : Timer
28 {
29     this()
30     {
31     	sysTimer = new SysTimer;
32     	semaphore = new Semaphore;
33 		auto thread = new Thread(&threadProc);
34 		thread.isDaemon = true;
35 		thread.start();
36     } ///
37 
38 protected:
39     SysTimer sysTimer;
40     Semaphore semaphore;
41     shared bool prodding;
42 
43 	override TimerEvent setTimeout (AppCallback fn, uint ms) { return new ThreadTimerEvent(fn, ms, false); }
44 	override TimerEvent setInterval(AppCallback fn, uint ms) { return new ThreadTimerEvent(fn, ms, true ); }
45 
46 private:
47 	void threadProc()
48 	{
49 		while (true)
50 		{
51 			Duration remainingTime;
52 
53 			synchronized(sysTimer)
54 			{
55                 auto now = MonoTime.currTime();
56 				prodding = true;
57 				sysTimer.prod(now);
58 				prodding = false;
59 
60                 now = MonoTime.currTime();
61 				remainingTime = sysTimer.getRemainingTime(now);
62 			}
63 
64 			if (remainingTime == Duration.max)
65 				semaphore.wait();
66 			else
67 				semaphore.wait(remainingTime);
68 		}
69 	}
70 
71 	final class ThreadTimerEvent : TimerEvent
72 	{
73 		AppCallback fn;
74 		bool recurring;
75 		TimerTask task;
76         uint ms;
77 
78 		this(AppCallback fn, uint ms, bool recurring)
79 		{
80 			auto now = MonoTime.currTime();
81 			this.fn = fn;
82             this.ms = ms;
83 			this.recurring = recurring;
84 			this.task = new TimerTask(&taskCallback);
85             synchronized(sysTimer)
86 				sysTimer.add(task, now + ms.msecs);
87 		}
88 
89 		void taskCallback(SysTimer timer, TimerTask task)
90 		{
91 			if (recurring)
92             {
93                 auto now = MonoTime.currTime();
94 				timer.add(task, now + ms.msecs);
95             }
96 			fn.call();
97 		}
98 
99 		override void cancel()
100 		{
101 			if (prodding) // cancel called from timer event handler, synchronization would cause a deadlock
102 				task.cancel();
103 			else
104 			synchronized(sysTimer)
105 				task.cancel();
106 		}
107 	}
108 }