1 module dast.async.timer.common;
2 
3 import dast.async.core;
4 import std.datetime;
5 
6 alias TickedHandler = void delegate(Object sender),
7 TimeoutHandler = void delegate(Object sender);
8 
9 /++
10 	Timing Wheel manger Class
11 +/
12 nothrow class TimingWheel {
13 	/++
14 		constructor
15 		Params:
16 			wheelSize = the Wheel's element router
17 	+/
18 	this(uint wheelSize) {
19 		if (wheelSize == 0)
20 			wheelSize = 2;
21 		_list = new NullWheelTimer[wheelSize];
22 		foreach (ref timer; _list)
23 			timer = new NullWheelTimer;
24 	}
25 
26 	/++
27 		add a Timer into the Wheel
28 		Params:
29 			tm = the timer
30 	+/
31 	void addTimer(WheelTimer tm, size_t wheel = 0) {
32 		size_t index;
33 		if (wheel > 0)
34 			index = nextWheel(wheel);
35 		else
36 			index = getPrev();
37 
38 		NullWheelTimer timer = _list[index];
39 		tm.next = timer.next;
40 		tm.prev = timer;
41 		if (timer.next)
42 			timer.next.prev = tm;
43 		timer.next = tm;
44 		tm.tw = this;
45 	}
46 
47 	/++
48 		The Wheel go forward
49 		Params:
50 			size = forward's element size;
51 		Notes:
52 			all forward's element will timeout
53 	+/
54 	void prevWheel(uint size = 1) {
55 		if (size == 0)
56 			return;
57 		foreach (i; 0 .. size) {
58 			NullWheelTimer timer = doNext();
59 			timer.onTimeout();
60 		}
61 	}
62 
63 protected:
64 	/// get next wheel times 's Wheel
65 	size_t nextWheel(size_t wheel) {
66 		auto next = wheel % _list.length;
67 		return (_now + next) % _list.length;
68 	}
69 
70 	/// get the index which is farthest with current index
71 	size_t getPrev() const => (_now ? _now : _list.length) - 1;
72 	/// go forward a element,and return the element
73 	NullWheelTimer doNext() {
74 		++_now;
75 		if (_now == _list.length)
76 			_now = 0;
77 		return _list[_now];
78 	}
79 	/// rest a timer
80 	void rest(WheelTimer tm, size_t next) {
81 		remove(tm);
82 		addTimer(tm, next);
83 	}
84 
85 	/// remove the timer
86 	void remove(WheelTimer tm) {
87 		tm.prev.next = tm.next;
88 		if (tm.next)
89 			tm.next.prev = tm.prev;
90 		tm.tw = null;
91 		tm.next = null;
92 		tm.prev = null;
93 	}
94 
95 private:
96 	NullWheelTimer[] _list;
97 	size_t _now;
98 }
99 
100 /++
101 	The timer parent's class
102 +/
103 nothrow abstract class WheelTimer {
104 	~this() {
105 		stop();
106 	}
107 	/++
108 		the function will be called when the timer timeout
109 	+/
110 	void onTimeout();
111 
112 	pragma(inline, true) final {
113 		/// rest the timer
114 		void rest(size_t next = 0) {
115 			if (tw) {
116 				tw.rest(this, next);
117 			}
118 		}
119 
120 		/// stop the time, it will remove from Wheel
121 		void stop() {
122 			if (tw)
123 				tw.remove(this);
124 		}
125 
126 		/// the time is active
127 		@property isActive() const => tw !is null;
128 	}
129 	/// whether the timer only run once
130 	bool oneShop;
131 
132 private:
133 	WheelTimer next;
134 	WheelTimer prev;
135 	TimingWheel tw;
136 }
137 
138 /// the Header Timer in the wheel
139 class NullWheelTimer : WheelTimer {
140 	override void onTimeout() {
141 		WheelTimer tm = next;
142 
143 		while (tm) {
144 			// WheelTimer timer = tm.next;
145 			if (tm.oneShop) {
146 				tm.stop();
147 			}
148 			tm.onTimeout();
149 			tm = tm.next;
150 		}
151 	}
152 }
153 
154 ///
155 unittest {
156 	import std.datetime;
157 	import core.thread;
158 	import std.exception;
159 	import tame.io.stdio;
160 
161 	class TestWheelTimer : WheelTimer {
162 		this() {
163 			time = Clock.currTime;
164 		}
165 
166 		override void onTimeout() nothrow @trusted {
167 			collectException(writeln("\nid is ", id, " \tcurrTime: ",
168 					Clock.currTime.toSimpleString(), "\tnew time: ", time.toSimpleString()));
169 		}
170 
171 		size_t id;
172 		private SysTime time;
173 	}
174 
175 	writeln("start");
176 	auto wheel = new TimingWheel(5);
177 	auto timers = new TestWheelTimer[5];
178 	foreach (tm; 0 .. 5) {
179 		timers[tm] = new TestWheelTimer;
180 	}
181 
182 	foreach (i, timer; timers) {
183 		timer.id = i;
184 		wheel.addTimer(timer);
185 		writeln("i = ", i);
186 	}
187 	writeln("prevWheel(5) _now = ", wheel._now);
188 	wheel.prevWheel(5);
189 	Thread.sleep(2.seconds);
190 	timers[4].stop();
191 	writeln("prevWheel(5) _now = ", wheel._now);
192 	wheel.prevWheel(5);
193 	Thread.sleep(2.seconds);
194 	writeln("prevWheel(3) _now = ", wheel._now);
195 	wheel.prevWheel(3);
196 	assert(wheel._now == 3);
197 	timers[2].rest();
198 	timers[4].rest();
199 	writeln("rest prevWheel(2) _now = ", wheel._now);
200 	wheel.prevWheel(2);
201 	assert(wheel._now == 0);
202 
203 	foreach (u; 0 .. 20) {
204 		Thread.sleep(2.seconds);
205 		writeln("prevWheel() _now = ", wheel._now);
206 		wheel.prevWheel();
207 	}
208 }
209 
210 nothrow:
211 
212 struct CustomTimer {
213 	enum MinTimeout = 50; // in ms
214 	enum WheelSize = 500;
215 	enum NextTimeout = cast(long)(MinTimeout * 2 / 3.0);
216 
217 	void initialize() {
218 		if (_wheel is null)
219 			_wheel = new TimingWheel(WheelSize);
220 		_nextTime = Clock.currStdTime / 10_000 + MinTimeout;
221 	}
222 
223 	int doWheel() {
224 		auto nowTime = Clock.currStdTime / 10_000;
225 		// trace("nowTime - _nextTime = ", nowTime - _nextTime);
226 		while (nowTime >= _nextTime) {
227 			_wheel.prevWheel();
228 			_nextTime += MinTimeout;
229 			nowTime = Clock.currStdTime / 10_000;
230 		}
231 		nowTime = _nextTime - nowTime;
232 		return cast(int)nowTime;
233 	}
234 
235 	@property TimingWheel timeWheel() => _wheel;
236 
237 private:
238 	TimingWheel _wheel;
239 	long _nextTime;
240 }
241 
242 abstract class Timer {
243 	protected bool _isActive;
244 	protected size_t _interval = 1000;
245 
246 	/// Timer tick handler - The handler will be handled in another thread
247 	TickedHandler ticked;
248 
249 	@property const nothrow @nogc {
250 		///
251 		bool isActive() => _isActive;
252 
253 		size_t wheelSize() => _wheelSize;
254 
255 		size_t time() => _interval;
256 
257 		/// in ms
258 		size_t interval() => _interval;
259 	}
260 
261 	/// ditto
262 	@property interval(size_t v) {
263 		_interval = v;
264 		return this;
265 	}
266 
267 	/// ditto
268 	@property interval(Duration duration) {
269 		_interval = cast(size_t)duration.total!"msecs";
270 		return this;
271 	}
272 
273 	void start() {
274 		_isActive = true;
275 	}
276 
277 	void stop() {
278 		_isActive = false;
279 	}
280 
281 	void reset(size_t interval) {
282 		_interval = interval;
283 		reset();
284 	}
285 
286 	void reset(Duration duration) {
287 		interval = duration;
288 		reset();
289 	}
290 
291 	void reset() {
292 		if (_isActive) {
293 			stop();
294 			start();
295 		}
296 	}
297 
298 	protected void onTick() {
299 		// trace("tick thread id: ", getTid());
300 		if (ticked)
301 			ticked(this);
302 	}
303 
304 protected:
305 	uint _wheelSize, _circle;
306 }
307 
308 class KissWheelTimer : WheelTimer {
309 	// override void onTimeout() nothrow
310 	// {
311 	//     collectException(trace("\nname is ", name, " \tcutterTime is : ",
312 	//             Clock.currTime.toSimpleString(), "\t new time is : ", time.toSimpleString()));
313 	// }
314 
315 	override void onTimeout() {
316 		_now++;
317 		if (_now >= _circle) {
318 			_now = 0;
319 			// rest(_wheelSize);
320 
321 			if (timeout)
322 				timeout(this);
323 		}
324 	}
325 
326 	TimeoutHandler timeout;
327 
328 private:
329 	// SysTime time;
330 	// uint _wheelSize;
331 	uint _circle;
332 	uint _now;
333 }