1 /** 2 * Management of timed events. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Simon Arlott 12 * Vladimir Panteleev <vladimir@thecybershadow.net> 13 */ 14 15 module ae.sys.timing; 16 17 public import core.time; 18 19 import std.exception; 20 21 /// Prototype for core.time.MonoTime (TickDuration replacement). 22 /// See https://github.com/D-Programming-Language/druntime/pull/711 23 static if (!is(core.time.MonoTime)) 24 { 25 struct MonoTime 26 { 27 enum max = MonoTime(ulong.max); 28 29 static MonoTime currTime() 30 { 31 return MonoTime(TickDuration.currSystemTick().hnsecs); 32 } 33 34 MonoTime opBinary(string op)(Duration d) const 35 if (op == "+") 36 { 37 return MonoTime(hnsecs + d.total!"hnsecs"); 38 } 39 40 Duration opBinary(string op)(MonoTime o) const 41 if (op == "-") 42 { 43 return dur!"hnsecs"(cast(long)(hnsecs - o.hnsecs)); 44 } 45 46 int opCmp(MonoTime o) const { return hnsecs == o.hnsecs ? 0 : hnsecs > o.hnsecs ? 1 : -1; } 47 48 private: 49 ulong hnsecs; 50 } 51 } 52 53 unittest 54 { 55 assert(MonoTime.init < MonoTime.max); 56 } 57 58 // TODO: allow customization of timing mechanism (alternatives to TickDuration)? 59 60 debug(TIMER) import std.stdio; 61 debug(TIMER_TRACK) import ae.utils.exception; 62 63 static this() 64 { 65 // Bug 6631 66 //enforce(TickDuration.ticksPerSec != 0, "TickDuration not available on this system"); 67 } 68 69 final class Timer 70 { 71 private: 72 TimerTask head; 73 TimerTask tail; 74 size_t count; 75 76 void add(TimerTask task, TimerTask start) 77 { 78 debug(TIMER_TRACK) task.additionStackTrace = getStackTrace(); 79 80 auto now = MonoTime.currTime(); 81 82 if (start !is null) 83 assert(start.owner is this); 84 85 task.owner = this; 86 task.prev = null; 87 task.next = null; 88 task.when = now + task.delay; 89 90 TimerTask tmp = start is null ? head : start; 91 92 while (tmp !is null) 93 { 94 if (task.when < tmp.when) 95 { 96 task.next = tmp; 97 task.prev = tmp.prev; 98 if (tmp.prev) 99 tmp.prev.next = task; 100 tmp.prev = task; 101 break; 102 } 103 tmp = tmp.next; 104 } 105 106 if (tmp is null) 107 { 108 if (head !is null) 109 { 110 tail.next = task; 111 task.prev = tail; 112 tail = task; 113 } 114 else 115 { 116 head = task; 117 tail = task; 118 } 119 } 120 else 121 if (tmp is head) 122 head = task; 123 124 assert(head is null || head.prev is null); 125 assert(tail is null || tail.next is null); 126 count++; 127 } 128 129 /// Unschedule a task. 130 void remove(TimerTask task) 131 { 132 debug (TIMER_VERBOSE) writefln("Removing a task which waits for %s.", task.delay); 133 assert(task.owner is this); 134 if (task is head) 135 { 136 if (head.next) 137 { 138 head = head.next; 139 head.prev = null; 140 debug (TIMER_VERBOSE) writefln("Removed current task, next task is waiting for %s (next at %s).", head.delay, head.when); 141 } 142 else 143 { 144 debug (TIMER_VERBOSE) writefln("Removed last task."); 145 assert(tail is task); 146 head = tail = null; 147 } 148 } 149 else 150 if (task is tail) 151 { 152 tail = task.prev; 153 if (tail) 154 tail.next = null; 155 } 156 else 157 { 158 TimerTask tmp = task.prev; 159 if (task.prev) 160 task.prev.next = task.next; 161 if (task.next) 162 { 163 task.next.prev = task.prev; 164 task.next = tmp; 165 } 166 } 167 task.owner = null; 168 task.next = task.prev = null; 169 count--; 170 } 171 172 void restart(TimerTask task) 173 { 174 TimerTask tmp; 175 176 assert(task.owner !is null, "This TimerTask is not active"); 177 assert(task.owner is this, "This TimerTask is not owned by this Timer"); 178 debug (TIMER_VERBOSE) writefln("Restarting a task which waits for %s.", task.delay); 179 180 // Store current position, as the new position must be after it 181 tmp = task.next !is null ? task.next : task.prev; 182 183 remove(task); 184 assert(task.owner is null); 185 186 add(task, tmp); 187 assert(task.owner is this); 188 } 189 190 public: 191 /// Pretend there are no tasks scheduled. 192 bool disabled; 193 194 /// Run scheduled tasks. 195 /// Returns true if any tasks ran. 196 bool prod() 197 { 198 if (disabled) return false; 199 200 auto now = MonoTime.currTime(); 201 202 bool ran; 203 204 if (head !is null) 205 { 206 while (head !is null && head.when <= now) 207 { 208 TimerTask task = head; 209 remove(head); 210 debug (TIMER) writefln("%s: Firing a task that waited for %s of %s.", now, task.delay + (now - task.when), task.delay); 211 if (task.handleTask) 212 task.handleTask(this, task); 213 ran = true; 214 } 215 216 debug (TIMER_VERBOSE) if (head !is null) writefln("Current task is waiting for %s, %s remaining.", head.delay, head.when - now); 217 } 218 219 return ran; 220 } 221 222 /// Add a new task to the timer. 223 void add(TimerTask task) 224 { 225 debug (TIMER_VERBOSE) writefln("Adding a task which waits for %s.", task.delay); 226 assert(task.owner is null, "This TimerTask is already active"); 227 add(task, null); 228 assert(task.owner is this); 229 assert(head !is null); 230 } 231 232 /// Return true if there are pending tasks scheduled. 233 bool isWaiting() 234 { 235 return !disabled && head !is null; 236 } 237 238 /// Return the MonoTime of the next scheduled task, or MonoTime.max if no tasks are scheduled. 239 MonoTime getNextEvent() 240 { 241 return disabled || head is null ? MonoTime.max : head.when; 242 } 243 244 /// Return the time until the first scheduled task, or Duration.max if no tasks are scheduled. 245 Duration getRemainingTime() 246 { 247 if (disabled || head is null) 248 return Duration.max; 249 250 auto now = MonoTime.currTime(); 251 252 debug(TIMER_TRACK) writefln("First timer due to fire in %s:\n\tCreated:\n\t\t%-(%s\n\t\t%)\n\tAdded:\n\t\t%-(%s\n\t\t%)", 253 head.when - now, head.creationStackTrace, head.additionStackTrace); 254 255 if (now < head.when) // "when" is in the future 256 return head.when - now; 257 else 258 return Duration.zero; 259 } 260 261 debug invariant() 262 { 263 if (head is null) 264 { 265 assert(tail is null); 266 assert(count == 0); 267 } 268 else 269 { 270 TimerTask t = cast(TimerTask)head; 271 assert(t.prev is null); 272 int n=1; 273 while (t.next) 274 { 275 assert(t.owner is this); 276 auto next = t.next; 277 assert(t is next.prev); 278 assert(t.when <= next.when); 279 t = next; 280 n++; 281 } 282 assert(t.owner is this); 283 assert(t is tail); 284 assert(count == n); 285 } 286 } 287 } 288 289 final class TimerTask 290 { 291 private: 292 Timer owner; 293 TimerTask prev; 294 TimerTask next; 295 296 MonoTime when; 297 Duration _delay; 298 299 debug(TIMER_TRACK) string[] creationStackTrace, additionStackTrace; 300 301 alias void delegate(Timer timer, TimerTask task) Handler; 302 303 public: 304 this(Duration delay, Handler handler = null) 305 { 306 assert(delay >= Duration.zero, "Creating TimerTask with a negative Duration"); 307 _delay = delay; 308 handleTask = handler; 309 debug(TIMER_TRACK) creationStackTrace = getStackTrace(); 310 } 311 312 /// Return whether the task is scheduled to run on a Timer. 313 bool isWaiting() 314 { 315 return owner !is null; 316 } 317 318 void cancel() 319 { 320 assert(isWaiting(), "This TimerTask is not active"); 321 owner.remove(this); 322 assert(!isWaiting()); 323 } 324 325 /// Reschedule the task to run with the same delay from now. 326 void restart() 327 { 328 assert(isWaiting(), "This TimerTask is not active"); 329 owner.restart(this); 330 assert(isWaiting()); 331 } 332 333 @property Duration delay() 334 { 335 return _delay; 336 } 337 338 @property void delay(Duration delay) 339 { 340 assert(delay >= Duration.zero, "Setting TimerTask delay to a negative Duration"); 341 assert(owner is null, "Changing duration of an active TimerTask"); 342 _delay = delay; 343 } 344 345 Handler handleTask; 346 } 347 348 /// The default timer 349 Timer mainTimer; 350 351 static this() 352 { 353 mainTimer = new Timer(); 354 } 355 356 // ******************************************************************************************************************** 357 358 TimerTask setTimeout(Args...)(void delegate(Args) handler, Duration delay, Args args) 359 { 360 auto task = new TimerTask(delay, (Timer timer, TimerTask task) { handler(args); }); 361 mainTimer.add(task); 362 return task; 363 } 364 365 TimerTask setInterval(Args...)(void delegate() handler, Duration delay, Args args) 366 { 367 auto task = new TimerTask(delay, (Timer timer, TimerTask task) { mainTimer.add(task); handler(args); }); 368 mainTimer.add(task); 369 return task; 370 } 371 372 void clearTimeout(TimerTask task) 373 { 374 task.cancel(); 375 } 376 377 // ******************************************************************************************************************** 378 379 /// Used to throttle actions to happen no more often than a certain period. 380 /// If last was less that span ago, return false. 381 /// Otherwise, update last to the current time and return true. 382 bool throttle(ref MonoTime last, Duration span) 383 { 384 MonoTime now = MonoTime.currTime(); 385 auto elapsed = now - last; 386 if (elapsed < span) 387 return false; 388 else 389 { 390 last = now; 391 return true; 392 } 393 } 394 395 // http://d.puremagic.com/issues/show_bug.cgi?id=7016 396 version(unittest) static import ae.utils.array; 397 398 unittest 399 { 400 import ae.utils.array; 401 import core.thread; 402 403 MonoTime[string] lastNag; 404 assert( lastNag.getOrAdd("cheese").throttle(10.msecs)); 405 assert(!lastNag.getOrAdd("cheese").throttle(10.msecs)); 406 Thread.sleep(20.msecs); 407 assert( lastNag.getOrAdd("cheese").throttle(10.msecs)); 408 assert(!lastNag.getOrAdd("cheese").throttle(10.msecs)); 409 }