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 }