1 /** 2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This module contain timer utilities. 7 */ 8 module distssh.timer; 9 10 import logger = std.experimental.logger; 11 import core.time : Duration, dur; 12 import std.datetime : SysTime, Clock; 13 import std.container.rbtree; 14 import std.typecons : Nullable; 15 16 @safe: 17 18 /// A collection of timers. 19 struct Timers { 20 private { 21 RedBlackTree!Timer timers; 22 Nullable!Timer front_; 23 } 24 25 auto put(Timer.Action action, Duration d) { 26 timers.stableInsert(Timer(Clock.currTime + d, action)); 27 } 28 29 auto put(Timer.Action action, SysTime t) { 30 timers.stableInsert(Timer(t, action)); 31 } 32 33 /// Get how long until the next timer expire. The minimum is minSleep. 34 Duration expireAt(Duration minSleep) nothrow { 35 import std.algorithm : max; 36 37 if (empty) { 38 return minSleep; 39 } 40 return max(minSleep, timers.front.expire - Clock.currTime); 41 } 42 43 /// Sleep until the next action triggers. 44 void sleep(Duration minSleep) @trusted { 45 import core.thread : Thread; 46 47 Thread.sleep(expireAt(minSleep)); 48 } 49 50 /// Sleep until the next action triggers and execute it, if there are any. 51 void tick(Duration minSleep) { 52 sleep(minSleep); 53 if (!empty) { 54 front.action(this); 55 popFront; 56 } 57 } 58 59 Timer front() pure nothrow { 60 assert(!empty, "Can't get front of an empty range"); 61 if (front_.isNull && !timers.empty) 62 front_ = timers.front; 63 return front_.get; 64 } 65 66 void popFront() { 67 assert(!empty, "Can't pop front of an empty range"); 68 if (!front_.isNull) { 69 timers.removeKey(front_.get); 70 front_.nullify; 71 } else { 72 timers.removeFront; 73 } 74 } 75 76 bool empty() pure nothrow @nogc { 77 return timers.empty && front_.isNull; 78 } 79 } 80 81 auto makeTimers() { 82 return Timers(new RedBlackTree!Timer); 83 } 84 85 /// An individual timer. 86 struct Timer { 87 private { 88 alias Action = void delegate(ref Timers); 89 SysTime expire; 90 size_t id; 91 } 92 93 Action action; 94 95 this(SysTime expire, Action action) { 96 this.expire = expire; 97 this.action = action; 98 this.id = () @trusted { return cast(size_t)&action; }(); 99 } 100 101 bool opEquals()(auto ref const typeof(this) s) const { 102 return expire == s.expire && id == s.id; 103 } 104 105 int opCmp(ref const typeof(this) rhs) const { 106 // return -1 if "this" is less than rhs, 1 if bigger and zero equal 107 if (expire < rhs.expire) 108 return -1; 109 if (expire > rhs.expire) 110 return 1; 111 if (id < rhs.id) 112 return -1; 113 if (id > rhs.id) 114 return 1; 115 return 0; 116 } 117 } 118 119 @("shall pop the first timer;") 120 unittest { 121 int timerPopped; 122 auto timers = makeTimers; 123 124 timers.put((ref Timers) { timerPopped = 42; }, 1000.dur!"msecs"); 125 timers.put((ref Timers) { timerPopped = 2; }, 2.dur!"msecs"); 126 127 timers.sleep(1.dur!"msecs"); 128 timers.front.action(timers); 129 assert(timerPopped == 2); 130 } 131 132 alias IntervalAction = bool delegate(); 133 134 /// Timers that fire each interval as long as the action return true. 135 auto makeInterval(ref Timers ts, IntervalAction action, Duration interval) { 136 void repeatFn(ref Timers ts) @safe { 137 const res = action(); 138 if (res) { 139 ts.put(&repeatFn, interval); 140 } 141 } 142 143 ts.put(&repeatFn, interval); 144 } 145 146 @("shall remove the interval timer when it return false") 147 unittest { 148 int ticks; 149 auto timers = makeTimers; 150 151 makeInterval(timers, () { ticks++; return ticks < 3; }, 2.dur!"msecs"); 152 while (!timers.empty) { 153 timers.tick(Duration.zero); 154 } 155 156 assert(ticks == 3); 157 }