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 }