1 module libasync.timer;
2 
3 import libasync.types;
4 import libasync.events;
5 import std.datetime;
6 
7 final class AsyncTimer
8 {
9 
10 nothrow:
11 private:
12 	bool m_oneshot = true;
13 	fd_t m_timerId;
14 	EventLoop m_evLoop;
15 	TimerHandler m_evh;
16 	Duration m_timeout;
17 	bool m_shooting = false;
18 	bool m_rearmed = false;
19 
20 public:
21 	this(EventLoop evl)
22 	in { assert(evl !is null); }
23 	body { m_evLoop = evl; }
24 
25 	mixin DefStatus;
26 
27 	/// Returns the Duration that the timer will wait before calling the handler
28 	/// after it is run.
29 	@property Duration timeout() const {
30 		return m_timeout;
31 	}
32 
33 	/// Returns whether the timer is set to rearm itself (oneShot=false) or 
34 	/// if it will have to be rearmed (oneShot=true).
35 	@property bool oneShot() const {
36 		return m_oneshot;
37 	}
38 
39 	/// Sets the timer to become periodic. For a running timer, 
40 	/// this setting will take effect after the timer is expired (oneShot) or 
41 	/// after it is killed (periodic).
42 	typeof(this) periodic(bool b = true) 
43 	in { assert(m_timerId == 0 || m_oneshot); }
44 	body
45 	{
46 		m_oneshot = !b;
47 		return this;
48 	}
49 
50 	/// Sets or changes the duration on the timer. For a running timer, 
51 	/// this setting will take effect after the timer is expired (oneShot) or 
52 	/// after it is killed (periodic).
53 	typeof(this) duration(Duration dur) {
54 		m_timeout = dur;
55 		return this;
56 	}
57 
58 	/// Runs a non-periodic, oneshot timer once using the specified Duration as
59 	/// a timeout. The handler from the last call to run() will be reused.
60 	bool rearm(Duration dur)
61 	in { 
62 		assert(m_timeout > 0.seconds);
63 		// assert(m_shooting);
64 		assert(m_oneshot, "Cannot rearm a periodic timer, it must fist be killed.");
65 	}
66 	body {
67 		m_rearmed = true;
68 
69 		m_timerId = m_evLoop.run(this, m_evh, dur);
70 		m_timeout = dur;
71 
72 		if (m_timerId == 0)
73 			return false;
74 		else
75 			return true;
76 	}
77 
78 	/// Starts the timer using the delegate as an expiration callback.
79 	bool run(void delegate() del) 
80 	in { 
81 		assert(m_timeout > 0.seconds);
82 		assert(m_oneshot || !m_timerId, "Cannot rearm a periodic timer, it must fist be killed.");
83 	}
84 	body {
85 		TimerHandler handler;
86 		handler.del = del;
87 		handler.ctxt = this;
88 
89 		return run(handler);
90 	}
91 
92 	private bool run(TimerHandler cb) {
93 		m_evh = cb;
94 
95 		if (m_timerId)
96 			m_rearmed = true;
97 		else
98 			m_rearmed = false;
99 		m_timerId = m_evLoop.run(this, cb, m_timeout);
100 		// try writeln("Timer starting", m_timerId); catch {}
101 		if (m_timerId == 0)
102 			return false;
103 		else
104 			return true;
105 	}
106 
107 	/// Cleans up underlying OS resources. This is required to change the
108 	/// timer from periodic to oneshot, or before disposing of this object.
109 	bool kill() {
110 		return m_evLoop.kill(this);
111 	}
112 
113 package:
114 
115 	version(Posix) mixin EvInfoMixins;
116 	
117 	@property fd_t id() {
118 		return m_timerId;
119 	}
120 	
121 	@property void id(fd_t fd) {
122 		m_timerId = fd;
123 	}
124 
125 	@property void rearmed(bool b) {
126 		m_rearmed = b;
127 	}
128 
129 	@property bool rearmed() {
130 		return m_rearmed;
131 	}
132 
133 	/*void handler() {
134 		try m_evh();
135 		catch {}
136 		return;
137 	}*/
138 }
139 
140 package struct TimerHandler {
141 	AsyncTimer ctxt;
142 	void delegate() del;
143 	void opCall() {
144 		assert(ctxt !is null);
145 		ctxt.m_rearmed = false;
146 		ctxt.m_shooting = true;
147 		del();
148 		ctxt.m_shooting = false;
149 		return;
150 	}
151 }