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