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