1 /*
2  * Hunt - A refined core library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.event.timer.Common;
13 
14 import hunt.io.socket.Common;
15 import hunt.logging;
16 import hunt.Exceptions;
17 
18 import std.datetime;
19 import std.exception;
20 
21 enum CustomTimerMinTimeOut = 50; // in ms
22 enum CustomTimerWheelSize = 500;
23 enum CustomTimer_Next_TimeOut = cast(long)(CustomTimerMinTimeOut * (2.0 / 3.0));
24 
25 alias TickedEventHandler = void delegate(Object sender);
26 
27 alias UintObject = BaseTypeObject!uint;
28 
29 /**
30 */
31 interface ITimer {
32 
33     /// 
34     bool isActive();
35 
36     /// in ms
37     size_t interval();
38 
39     /// ditto
40     ITimer interval(size_t v);
41 
42     /// ditto
43     ITimer interval(Duration duration);
44 
45     ///
46     ITimer onTick(TickedEventHandler handler);
47 
48     /// immediately: true to call first event immediately
49     /// once: true to call timed event only once
50     void start(bool immediately = false, bool once = false);
51 
52     void stop();
53 
54     void reset(bool immediately = false, bool once = false);
55 
56     void reset(size_t interval);
57 
58     void reset(Duration duration);
59 }
60 
61 /**
62     Timing Wheel manger Class
63 */
64 final class TimingWheel {
65     /**
66         constructor
67         Params:
68             wheelSize =  the Wheel's element router.
69     */
70     this(uint wheelSize) {
71         if (wheelSize == 0)
72             wheelSize = 2;
73         _list = new NullWheelTimer[wheelSize];
74         for (int i = 0; i < wheelSize; ++i) {
75             _list[i] = new NullWheelTimer();
76         }
77     }
78 
79     /**
80         add a Timer into the Wheel
81         Params:
82             tm  = the timer.
83     */
84     pragma(inline) void addNewTimer(WheelTimer tm, size_t wheel = 0) {
85         size_t index;
86         if (wheel > 0)
87             index = nextWheel(wheel);
88         else
89             index = getPrev();
90 
91         NullWheelTimer timer = _list[index];
92         tm._next = timer._next;
93         tm._prev = timer;
94         if (timer._next)
95             timer._next._prev = tm;
96         timer._next = tm;
97         tm._manger = this;
98     }
99 
100     /**
101         The Wheel  go forward
102         Params:
103             size  = forward's element size;
104         Notes:
105             all forward's element will timeout.
106     */
107     void prevWheel(uint size = 1) {
108         if (size == 0)
109             return;
110         foreach (i; 0 .. size) {
111             NullWheelTimer timer = doNext();
112             timer.onTimeOut();
113         }
114     }
115 
116 protected:
117     /// get next wheel times 's Wheel
118     pragma(inline) size_t nextWheel(size_t wheel) {
119         auto next = wheel % _list.length;
120         return (_now + next) % _list.length;
121     }
122 
123     /// get the index whitch is farthest with current index.
124     size_t getPrev() {
125         if (_now == 0)
126             return (_list.length - 1);
127         else
128             return (_now - 1);
129     }
130     /// go forward a element,and return the element.
131     pragma(inline) NullWheelTimer doNext() {
132         ++_now;
133         if (_now == _list.length)
134             _now = 0;
135         return _list[_now];
136     }
137     /// rest a timer.
138     pragma(inline) void rest(WheelTimer tm, size_t next) {
139         remove(tm);
140         addNewTimer(tm, next);
141     }
142     /// remove the timer.
143     pragma(inline) void remove(WheelTimer tm) {
144         tm._prev._next = tm._next;
145         if (tm._next)
146             tm._next._prev = tm._prev;
147         tm._manger = null;
148         tm._next = null;
149         tm._prev = null;
150     }
151 
152 private:
153     NullWheelTimer[] _list;
154     size_t _now;
155 }
156 
157 /**
158     The timer parent's class.
159 */
160 abstract class WheelTimer {
161     ~this() {
162         stop();
163     }
164     /**
165         the function will be called when the timer timeout.
166     */
167     void onTimeOut();
168 
169     /// rest the timer.
170     pragma(inline) final void rest(size_t next = 0) {
171         if (_manger) {
172             _manger.rest(this, next);
173         }
174     }
175 
176     /// stop the time, it will remove from Wheel.
177     pragma(inline) final void stop() {
178         if (_manger) {
179             _manger.remove(this);
180         }
181     }
182 
183     /// the time is active.
184     pragma(inline, true) final bool isActive() {
185         return _manger !is null;
186     }
187 
188     /// get the timer only run once.
189     pragma(inline, true) final @property oneShop() {
190         return _oneShop;
191     }
192     /// set the timer only run once.
193     pragma(inline) final @property oneShop(bool one) {
194         _oneShop = one;
195     }
196 
197 private:
198     WheelTimer _next = null;
199     WheelTimer _prev = null;
200     TimingWheel _manger = null;
201     bool _oneShop = false;
202 }
203 
204 /// the Header Timer in the wheel.
205 class NullWheelTimer : WheelTimer {
206     override void onTimeOut() {
207         WheelTimer tm = _next;
208 
209         while (tm) {
210             // WheelTimer timer = tm._next;
211             if (tm.oneShop()) {
212                 tm.stop();
213             }
214             tm.onTimeOut();
215             tm = tm._next;
216         }
217     }
218 }
219 
220 // unittest {
221 //     import std.datetime;
222 //     import std.stdio;
223 //     import std.conv;
224 //     import core.thread;
225 //     import std.exception;
226 
227 //     @trusted class TestWheelTimer : WheelTimer {
228 //         this() {
229 //             time = Clock.currTime();
230 //         }
231 
232 //         override void onTimeOut() nothrow {
233 //             collectException(writeln("\nname is ", name, " \tcutterTime is : ",
234 //                     Clock.currTime().toSimpleString(), "\t new time is : ", time.toSimpleString()));
235 //         }
236 
237 //         string name;
238 //     private:
239 //         SysTime time;
240 //     }
241 
242 //     writeln("start");
243 //     TimingWheel wheel = new TimingWheel(5);
244 //     TestWheelTimer[] timers = new TestWheelTimer[5];
245 //     foreach (tm; 0 .. 5) {
246 //         timers[tm] = new TestWheelTimer();
247 //     }
248 
249 //     int i = 0;
250 //     foreach (timer; timers) {
251 //         timer.name = to!string(i);
252 //         wheel.addNewTimer(timer);
253 //         writeln("i  = ", i);
254 //         ++i;
255 
256 //     }
257 //     writeln("prevWheel(5) the _now  = ", wheel._now);
258 //     wheel.prevWheel(5);
259 //     Thread.sleep(2.seconds);
260 //     timers[4].stop();
261 //     writeln("prevWheel(5) the _now  = ", wheel._now);
262 //     wheel.prevWheel(5);
263 //     Thread.sleep(2.seconds);
264 //     writeln("prevWheel(3) the _now  = ", wheel._now);
265 //     wheel.prevWheel(3);
266 //     assert(wheel._now == 3);
267 //     timers[2].rest();
268 //     timers[4].rest();
269 //     writeln("rest prevWheel(2) the _now  = ", wheel._now);
270 //     wheel.prevWheel(2);
271 //     assert(wheel._now == 0);
272 
273 //     foreach (u; 0 .. 20) {
274 //         Thread.sleep(2.seconds);
275 //         writeln("prevWheel() the _now  = ", wheel._now);
276 //         wheel.prevWheel();
277 //     }
278 
279 // }
280 
281 /**
282 */
283 struct CustomTimer {
284     void init() {
285         if (_timeWheel is null)
286             _timeWheel = new TimingWheel(CustomTimerWheelSize);
287         _nextTime = (Clock.currStdTime() / 10000) + CustomTimerMinTimeOut;
288     }
289 
290     int doWheel() {
291         auto nowTime = (Clock.currStdTime() / 10000);
292         // tracef("nowTime - _nextTime = %d", nowTime - _nextTime);
293         while (nowTime >= _nextTime) {
294             _timeWheel.prevWheel();
295             _nextTime += CustomTimerMinTimeOut;
296             nowTime = (Clock.currStdTime() / 10000);
297         }
298         nowTime = _nextTime - nowTime;
299         return cast(int) nowTime;
300     }
301 
302     TimingWheel timeWheel() {
303         return _timeWheel;
304     }
305 
306 private:
307     TimingWheel _timeWheel;
308     long _nextTime;
309 }
310 
311 /**
312 */
313 abstract class TimerChannelBase : AbstractChannel, ITimer {
314 
315     protected bool _isActive = false;
316     protected size_t _interval = 1000;
317 
318     /// Timer tick handler
319     TickedEventHandler ticked;
320 
321     this(Selector loop) {
322         super(loop, ChannelType.Timer);
323         _timeOut = 50;
324     }
325 
326     /// 
327     @property bool isActive() {
328         return _isActive;
329     }
330 
331     /// in ms
332     @property size_t interval() {
333         return _interval;
334     }
335 
336     /// ditto
337     @property ITimer interval(size_t v) {
338         _interval = v;
339         return this;
340     }
341 
342     /// ditto
343     @property ITimer interval(Duration duration) {
344         _interval = cast(size_t) duration.total!("msecs");
345         return this;
346     }
347 
348     /// The handler will be handled in another thread.
349     ITimer onTick(TickedEventHandler handler) {
350         this.ticked = handler;
351         return this;
352     }
353 
354     @property size_t wheelSize() {
355         return _wheelSize;
356     }
357 
358     @property size_t time() {
359         return _interval;
360     }
361 
362     void start(bool immediately = false, bool once = false) {
363         _inLoop.register(this);
364         _isRegistered = true;
365         _isActive = true;
366     }
367 
368     void stop() {
369         if (_isActive) {
370             _isActive = false;
371             onClose();
372         }
373     }
374 
375     void reset(size_t interval) {
376         this.interval = interval;
377         reset();
378     }
379 
380     void reset(Duration duration) {
381         this.interval = duration;
382         reset();
383     }
384 
385     void reset(bool immediately = false, bool once = false) {
386         if (_isActive) {
387             stop();
388             start();
389         }
390     }
391 
392     override void close() {
393         onClose();
394     }
395 
396     protected void onTick() {
397         // trace("tick thread id: ", getTid());
398         if (ticked !is null)
399             ticked(this);
400     }
401 
402 protected:
403     uint _wheelSize;
404     uint _circle;
405     size_t _timeOut;
406 }
407 
408 alias TimeoutHandler = void delegate(Object sender);
409 
410 /**
411 */
412 class HuntWheelTimer : WheelTimer {
413     this() {
414         // time = Clock.currTime();
415     }
416 
417     // override void onTimeOut() nothrow
418     // {
419     //     collectException(trace("\nname is ", name, " \tcutterTime is : ",
420     //             Clock.currTime().toSimpleString(), "\t new time is : ", time.toSimpleString()));
421     // }
422 
423     override void onTimeOut() {
424         _now++;
425         if (_now >= _circle) {
426             _now = 0;
427             if (timeout !is null) {
428                 timeout(this);
429             }
430         }
431     }
432 
433     TimeoutHandler timeout;
434 
435 private:
436     // SysTime time;
437     // uint _wheelSize;
438     uint _circle;
439     uint _now = 0;
440 }