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