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