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