1 module pixelperfectengine.system.timer;
2 /*
3  * Copyright (C) 2015-2021, by Laszlo Szeremi under the Boost license.
4  *
5  * Pixel Perfect Engine, system.timer module
6  */
7 import collections.sortedlist;
8 import std.typecons : BitFlags;
9 public import core.time;
10 
11 static CoarseTimer timer;
12 
13 static this() {
14 	timer = new CoarseTimer();
15 }
16 /**
17  * Implements a coarse timer, that checks the time periodically (e.g. on VSYNC), then calls the delegate if the time
18  * assigned to it has been lapsed.
19  * Is fast and can effectively test for multiple elements, but is inaccurate which can even fluctuate if tests are done
20  * on VSYNC intervals. This will make the duration longer in every case (up to 16.7ms on 60Hz displays), but this
21  * still should be accurate enough for many cases.
22  * In theory, calls for test can be done separate from VSYNC intervals by running it in its own thread, but that can 
23  * cause potential race issues.
24  * Delegates take the `jitter` argument, which is the overshoot of the time.
25  * Can be suspended for game pausing, etc., while keeping time deltas mostly accurate. Multiple instances of this class
26  * can be used to create various effects from suspending the timer.
27  */
28 public class CoarseTimer {
29 	alias TimerReceiver = void delegate(Duration jitter);
30 	/**
31 	 * A timer entry.
32 	 */
33 	protected struct Entry {
34 		TimerReceiver	onLapse;	///The delegate to be called.
35 		MonoTime		when;		///When the delegate must be called, in system time.
36 		int opCmp(const Entry other) const @nogc @trusted pure nothrow {
37 			return when.opCmp(other.when);
38 		}
39 		bool opEquals(const Entry other) const @nogc @trusted pure nothrow {
40 			return when == other.when;
41 		}
42 		size_t toHash() const @nogc @safe pure nothrow {
43 			return cast(size_t)when.ticks;
44 		}
45 	}
46 	protected enum StatusFlags {
47 		isTesting	=	1<<0,
48 		isPaused	=	1<<1,
49 	}
50 	protected SortedList!Entry	timerList;		///The list of timer entries.
51 	protected Entry[]			timerRegs;		///Secondary timer list.
52 	protected BitFlags!StatusFlags status;		///Contains various status flags.
53 	protected MonoTime			timeSuspend;	///Time when suspension happened.
54 	///CTOR (empty)
55 	public this() @safe pure nothrow {
56 
57 	}
58 	/**
59 	 * Registers an entry for the timer.
60 	 * Params:
61 	 *    dg = the delegate to be called when the event is lapsed.
62 	 *    delta = sets the amount of time into the future.
63 	 */
64 	public void register(TimerReceiver dg, Duration delta) @safe nothrow {
65 		if (!status.isTesting)
66 			timerList.put(Entry(dg, MonoTime.currTime + delta));
67 		else
68 			timerRegs ~= Entry(dg, MonoTime.currTime + delta);
69 	}
70 	/**
71 	 * Tests the entries.
72 	 * If enough time has passed, then those entries will be called and deleted.
73 	 */
74 	public void test() {
75 		status.isTesting = true;
76 		while (timerList.length && !status.isPaused) {
77 			if (MonoTime.currTime >= timerList[0].when) {
78 				timerList[0].onLapse(MonoTime.currTime - timerList[0].when);
79  				timerList.remove(0);
80 			} else {
81 				break;
82 			}
83 		}
84 		foreach (e ; timerRegs)
85 			timerList.put(e);
86 		timerRegs.length = 0;
87 		status.isTesting = true;
88 	}
89 	/** 
90 	 * Suspends the timer and saves the current timestamp to calculate timeshift caused by suspension.
91 	 */
92 	public void suspendTimer() {
93 		if(!status.isPaused) {
94 			status.isPaused = true;
95 			timeSuspend = MonoTime.currTime;
96 		}
97 	}
98 	alias suspend = suspendTimer;
99 	/** 
100 	 * Resumes timer and shifts all entries by given time delta.
101 	 */
102 	public void resumeTimer() {
103 		if(status.isPaused) {
104 			status.isPaused = false;
105 			const MonoTime timeResume = MonoTime.currTime;
106 			const Duration timeShift = timeResume - timeSuspend;
107 			//in theory, every entry should keep their position, so no list rebuilding is needed
108 			foreach (ref Entry key; timerList) {
109 				key.when += timeShift;
110 			}
111 		}
112 	}
113 	alias resume = resumeTimer;
114 }