1 /** 2 Efficient timer management routines for large number of timers. 3 4 Copyright: © 2014-2015 RejectedSoftware e.K. 5 Authors: Sönke Ludwig 6 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 7 */ 8 module vibe.core.drivers.timerqueue; 9 10 import vibe.utils.hashmap; 11 12 import std.datetime; 13 14 15 struct TimerQueue(DATA, long TIMER_RESOLUTION = 10_000) { 16 @safe: 17 18 static struct TimerInfo { 19 long timeout; // standard time 20 long repeatDuration; // hnsecs 21 bool pending; 22 DATA data; 23 } 24 25 private { 26 size_t m_timerIDCounter = 1; 27 HashMap!(size_t, TimerInfo) m_timers; 28 29 import std.container : Array, BinaryHeap; 30 BinaryHeap!(Array!TimeoutEntry, "a.timeout > b.timeout") m_timeoutHeap; 31 } 32 33 @property bool anyPending() { return !m_timeoutHeap.empty; } 34 35 size_t create(DATA data) 36 { 37 while (!m_timerIDCounter || m_timerIDCounter in m_timers) m_timerIDCounter++; 38 m_timers[m_timerIDCounter] = TimerInfo(0, 0, false, data); 39 return m_timerIDCounter++; 40 } 41 42 void destroy(size_t timer) 43 { 44 m_timers.remove(timer); 45 } 46 47 void schedule(size_t timer_id, Duration timeout_duration, bool periodic) 48 { 49 auto timeout = Clock.currStdTime() + timeout_duration.total!"hnsecs"; 50 auto pt = timer_id in m_timers; 51 assert(pt !is null, "Accessing non-existent timer ID."); 52 pt.timeout = timeout; 53 pt.repeatDuration = periodic ? timeout_duration.total!"hnsecs" : 0; 54 pt.pending = true; 55 //logDebugV("rearming timer %s in %s s", timer_id, dur.total!"usecs" * 1e-6); 56 scheduleTimer(timeout, timer_id); 57 } 58 59 void unschedule(size_t timer_id) 60 { 61 //logTrace("Stopping timer %s", timer_id); 62 auto pt = timer_id in m_timers; 63 pt.pending = false; 64 } 65 66 ref inout(DATA) getUserData(size_t timer_id) inout { return m_timers[timer_id].data; } 67 68 bool isPending(size_t timer_id) const { return m_timers.length > 0 && m_timers[timer_id].pending; } 69 70 bool isPeriodic(size_t timer_id) const { return m_timers.length > 0 && m_timers[timer_id].repeatDuration > 0; } 71 72 SysTime getFirstTimeout() 73 { 74 if (m_timeoutHeap.empty) return SysTime.max; 75 else return SysTime(m_timeoutHeap.front.timeout, UTC()); 76 } 77 78 void consumeTimeouts(SysTime now, scope void delegate(size_t timer, bool periodic, ref DATA data) @safe del) 79 { 80 //if (m_timeoutHeap.empty) logTrace("no timers scheduled"); 81 //else logTrace("first timeout: %s", (m_timeoutHeap.front.timeout - now) * 1e-7); 82 83 while (!m_timeoutHeap.empty && (m_timeoutHeap.front.timeout - now.stdTime) / TIMER_RESOLUTION <= 0) { 84 auto tm = m_timeoutHeap.front; 85 () @trusted { m_timeoutHeap.removeFront(); } (); 86 87 auto pt = tm.id in m_timers; 88 if (!pt || !pt.pending || pt.timeout != tm.timeout) continue; 89 90 if (pt.repeatDuration > 0) { 91 auto nskipped = (now.stdTime - pt.timeout) / pt.repeatDuration; 92 if (nskipped > 0) { 93 import vibe.core.log; 94 logDebugV("Skipped %s iterations of repeating timer %s (%s ms).", 95 nskipped, tm.id, pt.repeatDuration / 10_000); 96 } 97 pt.timeout += (1 + nskipped) * pt.repeatDuration; 98 scheduleTimer(pt.timeout, tm.id); 99 } else pt.pending = false; 100 101 //logTrace("Timer %s fired (%s/%s)", tm.id, owner != Task.init, callback !is null); 102 103 del(tm.id, pt.repeatDuration > 0, pt.data); 104 } 105 } 106 107 private void scheduleTimer(long timeout, size_t id) 108 { 109 //logTrace("Schedule timer %s", id); 110 auto entry = TimeoutEntry(timeout, id); 111 () @trusted { m_timeoutHeap.insert(entry); } (); 112 //logDebugV("first timer %s in %s s", id, (timeout - now) * 1e-7); 113 } 114 } 115 116 private struct TimeoutEntry { 117 long timeout; 118 size_t id; 119 }