-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
UniTimer.mc
173 lines (146 loc) · 5.59 KB
/
UniTimer.mc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
using Toybox.Timer;
module UniTimer {
class UptimeClock {
private var baseTimer = 0l;
private var prevTimer;
function initialize() {
prevTimer = System.getTimer();
}
function getUptime() {
var currentTimer = System.getTimer();
if (currentTimer < prevTimer) {
baseTimer += prevTimer;
}
prevTimer = currentTimer;
return baseTimer + currentTimer;
}
}
class Timer {
private const DELAY_MIN = 1;
// Allowed delay delta tolerance value; if delta between requested delay and scheduled
// current delay falls into this value, rescheduling will be skipped. Preventing
// rescheduling leads to increase of performance by omitting overhead recreation of system
// timer and reducing potential offset relative to actual scheduled delay.
private const DELTA_TOLERANCE = 15;
private var timer = new Timer.Timer();
private var clock = new UptimeClock();
private var scheduledTimers = {};
private var nextTick = null;
private var currentDelay = null;
function start(key as String, callback as Method(), delay as Number, repeat as Boolean) {
if (!scheduledTimers.hasKey(key)) {
scheduledTimers.put(
key,
new TimerEntry(callback, clock.getUptime(), delay, repeat)
);
if (scheduledTimers.size() == 1) {
startInternalTimer(delay);
} else {
if (nextTick == null || nextTick > clock.getUptime() + delay) {
stopInternalTimer();
startInternalTimer(delay);
}
}
}
}
function stop(key as String) {
scheduledTimers.remove(key);
if (scheduledTimers.isEmpty()) {
stopInternalTimer();
}
}
function isActive(key as String) {
return scheduledTimers.hasKey(key);
}
function onTick() {
var expiredTimers = [];
// Copy timer keys array to avoid possible concurrent moification of scheduledTimers
// dictionary in case of timer stop by the consumer in callback while permofming tick
// handling.
var ongoingTimerKeys = scheduledTimers.keys().slice(0, scheduledTimers.size());
for (var i = 0; i < ongoingTimerKeys.size(); i++) {
var currentTick = clock.getUptime();
var entry = scheduledTimers.get(ongoingTimerKeys[i]);
var expired = entry == null;
if (entry != null && entry.getNextTick() <= currentTick) {
entry.getCallback().invoke();
if (entry.getRepeat()) {
entry.incrementNextTick();
} else {
expired = true;
}
}
if (expired) {
expiredTimers.add(ongoingTimerKeys[i]);
}
}
for (var i = 0; i < expiredTimers.size(); i++) {
stop(expiredTimers[i]);
}
// Run scheduling round separately to handle possible added timers during callbacks
var minDelay = null;
for (var i = 0; i < scheduledTimers.size(); i++) {
var currentTick = clock.getUptime();
var nextTickDelta = scheduledTimers.values()[i].getNextTick() - currentTick;
if (minDelay == null || nextTickDelta < minDelay) {
minDelay = nextTickDelta;
}
}
if (!scheduledTimers.isEmpty() && minDelay != null) {
startInternalTimer(Mathx.max(minDelay, DELAY_MIN));
}
}
private function stopInternalTimer() {
timer.stop();
nextTick = null;
currentDelay = null;
}
private function startInternalTimer(delay) {
var delayDelta = 0;
if (currentDelay != null) {
delayDelta = currentDelay - delay;
}
if (
currentDelay == null ||
delayDelta > DELTA_TOLERANCE ||
(delayDelta < 0 && delay > DELTA_TOLERANCE)
) {
currentDelay = delay;
timer.start(method(:onTick), delay, true);
}
nextTick = clock.getUptime() + currentDelay;
}
class TimerEntry {
private var callback as Method();
private var delay as Number;
private var repeat as Boolean;
private var nextTick as Long;
function initialize(callback as Method(), uptime, delay as Number, repeat as Boolean) {
me.callback = callback;
me.delay = delay;
me.repeat = repeat;
nextTick = uptime + delay;
}
function getCallback() {
return callback;
}
function getRepeat() {
return repeat;
}
function getNextTick() {
return nextTick;
}
function incrementNextTick() {
nextTick += delay;
return nextTick;
}
}
}
var timerInstance;
function getTimer() {
if (timerInstance == null) {
timerInstance = new UniTimer.Timer();
}
return timerInstance;
}
}