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