Merge pull request #466 from

maciej-filip-sz:DelayedFunctionScheduler-patch

Sets and executes timeouts set during a tick.
This commit is contained in:
Sheel Choksi
2013-11-17 17:17:31 -08:00
parent 555d328cf2
commit 6bc87ad223

View File

@@ -983,14 +983,17 @@ getJasmineRequireObj().Clock = function() {
getJasmineRequireObj().DelayedFunctionScheduler = function() { getJasmineRequireObj().DelayedFunctionScheduler = function() {
function DelayedFunctionScheduler() { function DelayedFunctionScheduler() {
var self = this; var self = this;
var scheduledLookup = [];
var scheduledFunctions = {}; var scheduledFunctions = {};
var currentTime = 0; var currentTime = 0;
var delayedFnCount = 0; var delayedFnCount = 0;
self.tick = function(millis) { self.tick = function(millis) {
millis = millis || 0; millis = millis || 0;
currentTime = currentTime + millis; var endTime = currentTime + millis;
runFunctionsWithinRange(currentTime - millis, currentTime);
runScheduledFunctions(endTime);
currentTime = endTime;
}; };
self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) {
@@ -1006,7 +1009,8 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() {
millis = millis || 0; millis = millis || 0;
timeoutKey = timeoutKey || ++delayedFnCount; timeoutKey = timeoutKey || ++delayedFnCount;
runAtMillis = runAtMillis || (currentTime + millis); runAtMillis = runAtMillis || (currentTime + millis);
scheduledFunctions[timeoutKey] = {
var funcToSchedule = {
runAtMillis: runAtMillis, runAtMillis: runAtMillis,
funcToCall: f, funcToCall: f,
recurring: recurring, recurring: recurring,
@@ -1014,58 +1018,73 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() {
timeoutKey: timeoutKey, timeoutKey: timeoutKey,
millis: millis 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; return timeoutKey;
}; };
self.removeFunctionWithId = function(timeoutKey) { self.removeFunctionWithId = function(timeoutKey) {
delete scheduledFunctions[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() { self.reset = function() {
currentTime = 0; currentTime = 0;
scheduledLookup = [];
scheduledFunctions = {}; scheduledFunctions = {};
delayedFnCount = 0; delayedFnCount = 0;
}; };
return self; return self;
// finds/dupes functions within range and removes them. function indexOfFirstToPass(array, testFn) {
function functionsWithinRange(startMillis, endMillis) { var index = -1;
var fnsToRun = [];
for (var timeoutKey in scheduledFunctions) {
var scheduledFunc = scheduledFunctions[timeoutKey];
if (scheduledFunc &&
scheduledFunc.runAtMillis >= startMillis &&
scheduledFunc.runAtMillis <= endMillis) {
// remove fn -- we'll reschedule later if it is recurring. for (var i = 0; i < array.length; ++i) {
self.removeFunctionWithId(timeoutKey); if (testFn(array[i])) {
if (!scheduledFunc.recurring) { index = i;
fnsToRun.push(scheduledFunc); // schedules each function only once break;
} else {
fnsToRun.push(buildNthInstanceOf(scheduledFunc, 0));
var additionalTimesFnRunsInRange =
Math.floor((endMillis - scheduledFunc.runAtMillis) / scheduledFunc.millis);
for (var i = 0; i < additionalTimesFnRunsInRange; i++) {
fnsToRun.push(buildNthInstanceOf(scheduledFunc, i + 1));
}
reschedule(buildNthInstanceOf(scheduledFunc, additionalTimesFnRunsInRange));
}
} }
} }
return fnsToRun; return index;
} }
function buildNthInstanceOf(scheduledFunc, n) { function deleteFromLookup(key) {
return { var value = Number(key);
runAtMillis: scheduledFunc.runAtMillis + (scheduledFunc.millis * n), var i = indexOfFirstToPass(scheduledLookup, function (millis) {
funcToCall: scheduledFunc.funcToCall, return millis === value;
params: scheduledFunc.params, });
millis: scheduledFunc.millis,
recurring: scheduledFunc.recurring, if (i > -1) {
timeoutKey: scheduledFunc.timeoutKey scheduledLookup.splice(i, 1);
}; }
} }
function reschedule(scheduledFn) { function reschedule(scheduledFn) {
@@ -1077,21 +1096,30 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() {
scheduledFn.runAtMillis + scheduledFn.millis); scheduledFn.runAtMillis + scheduledFn.millis);
} }
function runScheduledFunctions(endTime) {
function runFunctionsWithinRange(startMillis, endMillis) { if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
var funcsToRun = functionsWithinRange(startMillis, endMillis);
if (funcsToRun.length === 0) {
return; return;
} }
funcsToRun.sort(function(a, b) { do {
return a.runAtMillis - b.runAtMillis; currentTime = scheduledLookup.shift();
});
for (var i = 0; i < funcsToRun.length; ++i) { var funcsToRun = scheduledFunctions[currentTime];
var funcToRun = funcsToRun[i]; delete scheduledFunctions[currentTime];
funcToRun.funcToCall.apply(null, funcToRun.params || []);
} 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);
} }
} }