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 my.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 void put(Timer.Action action, Duration d) @trusted { 26 timers.stableInsert(Timer(Clock.currTime + d, action)); 27 } 28 29 void put(Timer.Action action, SysTime t) @trusted { 30 timers.stableInsert(Timer(t, action)); 31 } 32 33 /// Get how long until the next timer expire. The default is defaultSleep. 34 Duration expireAt(Duration defaultSleep) nothrow { 35 import std.algorithm : max; 36 37 if (empty) { 38 return defaultSleep; 39 } 40 return max(Duration.zero, timers.front.expire - Clock.currTime); 41 } 42 43 /// Sleep until the next action triggers. 44 void sleep(Duration defaultSleep) @trusted { 45 import core.thread : Thread; 46 47 Thread.sleep(expireAt(defaultSleep)); 48 } 49 50 /// Sleep until the next action triggers and execute it, if there are any. 51 void tick(Duration defaultSleep) { 52 sleep(defaultSleep); 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 size_t toHash() pure nothrow const @nogc scope { 102 return expire.toHash.hashOf(id); // mixing two hash values 103 } 104 105 bool opEquals()(auto ref const typeof(this) s) const { 106 return expire == s.expire && id == s.id; 107 } 108 109 int opCmp(ref const typeof(this) rhs) const { 110 // return -1 if "this" is less than rhs, 1 if bigger and zero equal 111 if (expire < rhs.expire) 112 return -1; 113 if (expire > rhs.expire) 114 return 1; 115 if (id < rhs.id) 116 return -1; 117 if (id > rhs.id) 118 return 1; 119 return 0; 120 } 121 } 122 123 @("shall pop the first timer;") 124 unittest { 125 int timerPopped; 126 auto timers = makeTimers; 127 128 timers.put((ref Timers) { timerPopped = 42; }, 1000.dur!"msecs"); 129 timers.put((ref Timers) { timerPopped = 2; }, 2.dur!"msecs"); 130 131 timers.sleep(1.dur!"msecs"); 132 timers.front.action(timers); 133 assert(timerPopped == 2); 134 } 135 136 /// A negative duration mean it will be removed. 137 alias IntervalAction = Duration delegate(); 138 139 /// Timers that fire each interval. The intervals is adjusted by `action` and 140 /// removed if the interval is < 0. 141 auto makeInterval(ref Timers ts, IntervalAction action, Duration interval) { 142 void repeatFn(ref Timers ts) @safe { 143 const res = action(); 144 if (res >= Duration.zero) { 145 ts.put(&repeatFn, res); 146 } 147 } 148 149 ts.put(&repeatFn, interval); 150 } 151 152 @("shall remove the interval timer when it return false") 153 unittest { 154 int ticks; 155 auto timers = makeTimers; 156 157 makeInterval(timers, () { 158 ticks++; 159 if (ticks < 3) 160 return 2.dur!"msecs"; 161 return -1.dur!"seconds"; 162 }, 2.dur!"msecs"); 163 while (!timers.empty) { 164 timers.tick(Duration.zero); 165 } 166 167 assert(ticks == 3); 168 }