-
Notifications
You must be signed in to change notification settings - Fork 0
/
nervous-timer.js
202 lines (171 loc) · 4.89 KB
/
nervous-timer.js
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
( function () {
/*
nervousTimer.js
nervousTimer.js provides alternative setInterval() / setTimeout() implementation that runs based on real world's time.
Web browser's native setInterval funciton is running based on task queue, and if browser's timer thread was asleep, program can't know how much difference between process time and the time in real world. For example, mobile safari's timer thread could sleep when opening other tabs, or when phone's sleep button was clicked.
NervousTimer provides a timer that works based on time of real world. For example, you want to do some task for each minute in realtime on smartphone, and if user makes his device asleep for 42 minutes, then nervousTimer will invoke `skipped` callback to let you know there are 41 skipped periods, and fire just one `ontime` callback on just 42 minutes after the timer starts.
*/
"use strict";
var nervousTimer = {}
, loopInterval = 10
, onTimeRange = 40
, timers = {}
, lastId = 1
;
var mainLoop = function () {
var now = ( new Date() ).getTime()
, doneTimers = []
, timerId, timer, cont, i, doneLen
;
for ( timerId in timers ) {
timer = timers[timerId];
if ( timer.next <= now ) {
cont = timer.execute(now);
if ( !cont ) {
doneTimers.push(timerId);
}
}
}
doneLen = doneTimers.length;
for ( i=0; i < doneLen; i++ ) {
delete timers[doneTimers[i]];
}
};
var registerTimer = function (handler, args) {
var ontime = Array.prototype.shift.apply(args)
, skipped, interval, origin;
if ( 'number' !== typeof args[0] ) {
skipped = Array.prototype.shift.apply(args);
}
interval = args[0];
origin = args[1];
var start = ('undefined' !== typeof origin ) ? origin
: (new Date()).getTime()
;
timers[lastId] = {
index: 0
, interval: interval
, next: start + interval
, ontime: ontime
, skipped: skipped
, execute: handler
};
return lastId++;
};
var clearTimer = function (timerId) {
delete timers[timerId];
};
/*
* nervousTimer.setInterval(
* ontime( index ),
* interval,
* [skipped( index, num-of-skipped-period )],
* [origin]
* );
*
* When interval is 100ms, ontime callback could be fired only if
* internal timer runs at ( n * 100 ) +/- 10 ms. otherwise, skipped
* callback to be fired for interval periods that should be fired
* in the past.
*
* * = `ontime` period
* + = internal loop excutes
*
* 0 100 200 300
* world |------->*|*<----->*|*<----->*|*<---------
* thread |-+-+- <==( hard sleep )==> -+-+-+-+-+----
* ^
* |
* at this moment, `skipped` callback will be invoked with
* argument (0, 2), and `ontime` callback will be invoked
* after that.
*
* initial callback
*
* Unlike native setInterval, nervousTimer calls ontime (or skipped)
* callback at time 0.
*/
var intervalHandler = function (now) {
var duration = now - this.next;
var periods = 1 + Math.floor( duration / this.interval );
var fromLastPeriod = duration % this.interval;
var isOnTime = false;
var skippedPeriods = periods;
if ( fromLastPeriod < onTimeRange ) {
isOnTime = true;
skippedPeriods--;
}
if ( skippedPeriods > 0 && this.skipped) {
this.skipped(this.index + 1, skippedPeriods);
}
this.index += skippedPeriods;
if ( isOnTime ) {
this.index++;
this.ontime(this.index);
}
this.next += periods * this.interval;
return true;
};
nervousTimer.setInterval = function () {
return registerTimer( intervalHandler, arguments );
};
/*
* nervousTimer.clearInterval( timerId )
*/
nervousTimer.clearInterval = function ( timerId ) {
clearTimer(timerId);
};
/*
* nervousTimer.setTimeout(
* ontime(),
* interval,
* [skipped()],
* [origin]
* );
*
*
*/
var timeoutHandler = function (now) {
if ( now < this.next + onTimeRange ) {
this.ontime();
}
else {
if ( this.skipped ) {
this.skipped();
}
}
return false;
};
nervousTimer.setTimeout = function () {
return registerTimer( timeoutHandler, arguments );
};
/*
* nervousTimer.clearTimeout( timerId )
*/
nervousTimer.clearTimeout = function ( timerId ) {
clearTimer(timerId);
};
nervousTimer.clearAll = function () {
for ( var tid in timers ) {
delete timers[tid];
}
};
/*
* set interval of internal main loop
*/
nervousTimer.setLoopInterval = function (interval) {
if ( 'undefined' !== typeof mainTimerId ) {
clearInterval(mainTimerId);
}
loopInterval = interval;
mainTimerId = setInterval( mainLoop, loopInterval );
};
// Run main loop!
var mainTimerId = setInterval( mainLoop, loopInterval );
if ( typeof module !== 'undefined') {
module.exports = nervousTimer;
}
if ( typeof window !== 'undefined' ) {
window.nervousTimer = nervousTimer;
}
})();