All timeouts and intervals set during a tick were being scheduled to run at delay + end-of-tick, instead of delay + time-of-outer-timeout. Scheduled run-at times were shifted because currentTime was being incremented before executing scheduled functions. Additionally, the execute loop was iterating over a functions-to-run array, created from scheduledFunctions before starting. Any changes to scheduledFunctions were being ignored during the tick, and the next tick would ignore any functions which should have been executed in the past. The commit is a rewrite of DelayedFunctionScheduler, preserving the public interface. Execution of scheduled functions updates currentTime on each iteration, and each time takes the functions with the lowest runAtMillis from the schedule, if they aren't higher than endTime.
147 lines
3.9 KiB
JavaScript
147 lines
3.9 KiB
JavaScript
getJasmineRequireObj().DelayedFunctionScheduler = function() {
|
|
function DelayedFunctionScheduler() {
|
|
var self = this;
|
|
var scheduledLookup = [];
|
|
var scheduledFunctions = {};
|
|
var currentTime = 0;
|
|
var delayedFnCount = 0;
|
|
|
|
self.tick = function(millis) {
|
|
millis = millis || 0;
|
|
var endTime = currentTime + millis;
|
|
|
|
runScheduledFunctions(endTime);
|
|
};
|
|
|
|
self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) {
|
|
var f;
|
|
if (typeof(funcToCall) === 'string') {
|
|
/* jshint evil: true */
|
|
f = function() { return eval(funcToCall); };
|
|
/* jshint evil: false */
|
|
} else {
|
|
f = funcToCall;
|
|
}
|
|
|
|
millis = millis || 0;
|
|
timeoutKey = timeoutKey || ++delayedFnCount;
|
|
runAtMillis = runAtMillis || (currentTime + millis);
|
|
|
|
var funcToSchedule = {
|
|
runAtMillis: runAtMillis,
|
|
funcToCall: f,
|
|
recurring: recurring,
|
|
params: params,
|
|
timeoutKey: timeoutKey,
|
|
millis: millis
|
|
};
|
|
|
|
if (runAtMillis in scheduledFunctions) {
|
|
scheduledFunctions[runAtMillis].push(funcToSchedule);
|
|
} else {
|
|
scheduledFunctions[runAtMillis] = [funcToSchedule];
|
|
scheduledLookup.push(runAtMillis);
|
|
scheduledLookup.sort(function (a, b) {
|
|
return a - b;
|
|
});
|
|
}
|
|
|
|
return timeoutKey;
|
|
};
|
|
|
|
self.removeFunctionWithId = function(timeoutKey) {
|
|
for (var runAtMillis in scheduledFunctions) {
|
|
var funcs = scheduledFunctions[runAtMillis];
|
|
var i = indexOfFirstToPass(funcs, function (func) {
|
|
return func.timeoutKey === timeoutKey;
|
|
});
|
|
|
|
if (i > -1) {
|
|
if (funcs.length === 1) {
|
|
delete scheduledFunctions[runAtMillis];
|
|
deleteFromLookup(runAtMillis);
|
|
} else {
|
|
funcs.splice(i, 1);
|
|
}
|
|
|
|
// intervals get rescheduled when executed, so there's never more
|
|
// than a single scheduled function with a given timeoutKey
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
self.reset = function() {
|
|
currentTime = 0;
|
|
scheduledLookup = [];
|
|
scheduledFunctions = {};
|
|
delayedFnCount = 0;
|
|
};
|
|
|
|
return self;
|
|
|
|
function indexOfFirstToPass(array, testFn) {
|
|
var index = -1;
|
|
|
|
for (var i = 0; i < array.length; ++i) {
|
|
if (testFn(array[i])) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
function deleteFromLookup(key) {
|
|
var value = Number(key);
|
|
var i = indexOfFirstToPass(scheduledLookup, function (millis) {
|
|
return millis === value;
|
|
});
|
|
|
|
if (i > -1) {
|
|
scheduledLookup.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
function reschedule(scheduledFn) {
|
|
self.scheduleFunction(scheduledFn.funcToCall,
|
|
scheduledFn.millis,
|
|
scheduledFn.params,
|
|
true,
|
|
scheduledFn.timeoutKey,
|
|
scheduledFn.runAtMillis + scheduledFn.millis);
|
|
}
|
|
|
|
function runScheduledFunctions(endTime) {
|
|
if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
|
|
return;
|
|
}
|
|
|
|
do {
|
|
currentTime = scheduledLookup.shift();
|
|
|
|
var funcsToRun = scheduledFunctions[currentTime];
|
|
delete scheduledFunctions[currentTime];
|
|
|
|
for (var i = 0; i < funcsToRun.length; ++i) {
|
|
var funcToRun = funcsToRun[i];
|
|
funcToRun.funcToCall.apply(null, funcToRun.params || []);
|
|
|
|
if (funcToRun.recurring) {
|
|
reschedule(funcToRun);
|
|
}
|
|
}
|
|
} while (scheduledLookup.length > 0 &&
|
|
// checking first if we're out of time prevents setTimeout(0)
|
|
// scheduled in a funcToRun from forcing an extra iteration
|
|
currentTime !== endTime &&
|
|
scheduledLookup[0] <= endTime);
|
|
|
|
currentTime = endTime;
|
|
}
|
|
}
|
|
|
|
return DelayedFunctionScheduler;
|
|
};
|