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_VERBOSE) writefln("Adding a task which waits for %s.", task.delay); 79 debug(TIMER_TRACK) task.additionStackTrace = getStackTrace(); 80 81 auto now = MonoTime.currTime(); 82 83 if (start !is null) 84 assert(start.owner is this); 85 86 task.owner = this; 87 task.prev = null; 88 task.next = null; 89 task.when = now + task.delay; 90 91 TimerTask tmp = start is null ? head : start; 92 93 while (tmp !is null) 94 { 95 if (task.when < tmp.when) 96 { 97 task.next = tmp; 98 task.prev = tmp.prev; 99 if (tmp.prev) 100 tmp.prev.next = task; 101 tmp.prev = task; 102 break; 103 } 104 tmp = tmp.next; 105 } 106 107 if (tmp is null) 108 { 109 if (head !is null) 110 { 111 tail.next = task; 112 task.prev = tail; 113 tail = task; 114 } 115 else 116 { 117 head = task; 118 tail = task; 119 } 120 } 121 else 122 if (tmp is head) 123 head = task; 124 125 assert(head is null || head.prev is null); 126 assert(tail is null || tail.next is null); 127 count++; 128 } 129 130 /// Unschedule a task. 131 void remove(TimerTask task) 132 { 133 debug (TIMER_VERBOSE) writefln("Removing a task which waits for %s.", task.delay); 134 assert(task.owner is this); 135 if (task is head) 136 { 137 if (head.next) 138 { 139 head = head.next; 140 head.prev = null; 141 debug (TIMER_VERBOSE) writefln("Removed current task, next task is waiting for %s (next at %s).", head.delay, head.when); 142 } 143 else 144 { 145 debug (TIMER_VERBOSE) writefln("Removed last task."); 146 assert(tail is task); 147 head = tail = null; 148 } 149 } 150 else 151 if (task is tail) 152 { 153 tail = task.prev; 154 if (tail) 155 tail.next = null; 156 } 157 else 158 { 159 TimerTask tmp = task.prev; 160 if (task.prev) 161 task.prev.next = task.next; 162 if (task.next) 163 { 164 task.next.prev = task.prev; 165 task.next = tmp; 166 } 167 } 168 task.owner = null; 169 task.next = task.prev = null; 170 count--; 171 } 172 173 void restart(TimerTask task) 174 { 175 TimerTask tmp; 176 177 assert(task.owner !is null, "This TimerTask is not active"); 178 assert(task.owner is this, "This TimerTask is not owned by this Timer"); 179 debug (TIMER_VERBOSE) writefln("Restarting a task which waits for %s.", task.delay); 180 181 // Store current position, as the new position must be after it 182 tmp = task.next !is null ? task.next : task.prev; 183 184 remove(task); 185 assert(task.owner is null); 186 187 add(task, tmp); 188 assert(task.owner is this); 189 } 190 191 public: 192 /// Pretend there are no tasks scheduled. 193 bool disabled; 194 195 /// Run scheduled tasks. 196 /// Returns true if any tasks ran. 197 bool prod() 198 { 199 if (disabled) return false; 200 201 auto now = MonoTime.currTime(); 202 203 bool ran; 204 205 if (head !is null) 206 { 207 while (head !is null && head.when <= now) 208 { 209 TimerTask task = head; 210 remove(head); 211 debug (TIMER) writefln("%s: Firing a task that waited for %s of %s.", now, task.delay + (now - task.when), task.delay); 212 if (task.handleTask) 213 task.handleTask(this, task); 214 ran = true; 215 } 216 217 debug (TIMER_VERBOSE) if (head !is null) writefln("Current task is waiting for %s, %s remaining.", head.delay, head.when - now); 218 } 219 220 return ran; 221 } 222 223 /// Add a new task to the timer. 224 void add(TimerTask task) 225 { 226 debug (TIMER_VERBOSE) writefln("Adding a task which waits for %s.", task.delay); 227 assert(task.owner is null, "This TimerTask is already active"); 228 add(task, null); 229 assert(task.owner is this); 230 assert(head !is null); 231 } 232 233 /// Return true if there are pending tasks scheduled. 234 bool isWaiting() 235 { 236 return !disabled && head !is null; 237 } 238 239 /// Return the MonoTime of the next scheduled task, or MonoTime.max if no tasks are scheduled. 240 MonoTime getNextEvent() 241 { 242 return disabled || head is null ? MonoTime.max : head.when; 243 } 244 245 /// Return the time until the first scheduled task, or Duration.max if no tasks are scheduled. 246 Duration getRemainingTime() 247 { 248 if (disabled || head is null) 249 return Duration.max; 250 251 auto now = MonoTime.currTime(); 252 253 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%)", 254 head.when - now, head.creationStackTrace, head.additionStackTrace); 255 256 if (now < head.when) // "when" is in the future 257 return head.when - now; 258 else 259 return Duration.zero; 260 } 261 262 debug invariant() 263 { 264 if (head is null) 265 { 266 assert(tail is null); 267 assert(count == 0); 268 } 269 else 270 { 271 TimerTask t = cast(TimerTask)head; 272 assert(t.prev is null); 273 int n=1; 274 while (t.next) 275 { 276 assert(t.owner is this); 277 auto next = t.next; 278 assert(t is next.prev); 279 assert(t.when <= next.when); 280 t = next; 281 n++; 282 } 283 assert(t.owner is this); 284 assert(t is tail); 285 assert(count == n); 286 } 287 } 288 } 289 290 final class TimerTask 291 { 292 private: 293 Timer owner; 294 TimerTask prev; 295 TimerTask next; 296 297 MonoTime when; 298 Duration _delay; 299 300 debug(TIMER_TRACK) string[] creationStackTrace, additionStackTrace; 301 302 alias void delegate(Timer timer, TimerTask task) Handler; 303 304 public: 305 this(Duration delay, Handler handler = null) 306 { 307 assert(delay >= Duration.zero, "Creating TimerTask with a negative Duration"); 308 _delay = delay; 309 handleTask = handler; 310 debug(TIMER_TRACK) creationStackTrace = getStackTrace(); 311 } 312 313 /// Return whether the task is scheduled to run on a Timer. 314 bool isWaiting() 315 { 316 return owner !is null; 317 } 318 319 void cancel() 320 { 321 assert(isWaiting(), "This TimerTask is not active"); 322 owner.remove(this); 323 assert(!isWaiting()); 324 } 325 326 /// Reschedule the task to run with the same delay from now. 327 void restart() 328 { 329 assert(isWaiting(), "This TimerTask is not active"); 330 owner.restart(this); 331 assert(isWaiting()); 332 } 333 334 @property Duration delay() 335 { 336 return _delay; 337 } 338 339 @property void delay(Duration delay) 340 { 341 assert(delay >= Duration.zero, "Setting TimerTask delay to a negative Duration"); 342 assert(owner is null, "Changing duration of an active TimerTask"); 343 _delay = delay; 344 } 345 346 Handler handleTask; 347 } 348 349 /// The default timer 350 Timer mainTimer; 351 352 static this() 353 { 354 mainTimer = new Timer(); 355 } 356 357 // ******************************************************************************************************************** 358 359 TimerTask setTimeout(Args...)(void delegate(Args) handler, Duration delay, Args args) 360 { 361 auto task = new TimerTask(delay, (Timer timer, TimerTask task) { handler(args); }); 362 mainTimer.add(task); 363 return task; 364 } 365 366 TimerTask setInterval(Args...)(void delegate(Args) handler, Duration delay, Args args) 367 { 368 auto task = new TimerTask(delay, (Timer timer, TimerTask task) { mainTimer.add(task); handler(args); }); 369 mainTimer.add(task); 370 return task; 371 } 372 373 void clearTimeout(TimerTask task) 374 { 375 task.cancel(); 376 } 377 378 // ******************************************************************************************************************** 379 380 /// Used to throttle actions to happen no more often than a certain period. 381 /// If last was less that span ago, return false. 382 /// Otherwise, update last to the current time and return true. 383 bool throttle(ref MonoTime last, Duration span) 384 { 385 MonoTime now = MonoTime.currTime(); 386 auto elapsed = now - last; 387 if (elapsed < span) 388 return false; 389 else 390 { 391 last = now; 392 return true; 393 } 394 } 395 396 // http://d.puremagic.com/issues/show_bug.cgi?id=7016 397 version(unittest) static import ae.utils.array; 398 399 unittest 400 { 401 import ae.utils.array; 402 import core.thread; 403 404 MonoTime[string] lastNag; 405 assert( lastNag.getOrAdd("cheese").throttle(10.msecs)); 406 assert(!lastNag.getOrAdd("cheese").throttle(10.msecs)); 407 Thread.sleep(20.msecs); 408 assert( lastNag.getOrAdd("cheese").throttle(10.msecs)); 409 assert(!lastNag.getOrAdd("cheese").throttle(10.msecs)); 410 }