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