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.
247 lines
6.7 KiB
JavaScript
247 lines
6.7 KiB
JavaScript
describe("DelayedFunctionScheduler", function() {
|
|
it("schedules a function for later execution", function() {
|
|
var scheduler = new j$.DelayedFunctionScheduler(),
|
|
fn = jasmine.createSpy('fn');
|
|
|
|
scheduler.scheduleFunction(fn, 0);
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
|
|
scheduler.tick(0);
|
|
|
|
expect(fn).toHaveBeenCalled();
|
|
});
|
|
|
|
it("schedules a string for later execution", function() {
|
|
var scheduler = new j$.DelayedFunctionScheduler(),
|
|
strfn = "horrible = true;";
|
|
|
|
scheduler.scheduleFunction(strfn, 0);
|
|
|
|
scheduler.tick(0);
|
|
|
|
expect(horrible).toEqual(true);
|
|
});
|
|
|
|
it("#tick defaults to 0", function() {
|
|
var scheduler = new j$.DelayedFunctionScheduler(),
|
|
fn = jasmine.createSpy('fn');
|
|
|
|
scheduler.scheduleFunction(fn, 0);
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
|
|
scheduler.tick();
|
|
|
|
expect(fn).toHaveBeenCalled();
|
|
});
|
|
|
|
it("defaults delay to 0", function() {
|
|
var scheduler = new j$.DelayedFunctionScheduler(),
|
|
fn = jasmine.createSpy('fn');
|
|
|
|
scheduler.scheduleFunction(fn);
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
|
|
scheduler.tick(0);
|
|
|
|
expect(fn).toHaveBeenCalled();
|
|
});
|
|
|
|
it("optionally passes params to scheduled functions", function() {
|
|
var scheduler = new j$.DelayedFunctionScheduler(),
|
|
fn = jasmine.createSpy('fn');
|
|
|
|
scheduler.scheduleFunction(fn, 0, ['foo', 'bar']);
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
|
|
scheduler.tick(0);
|
|
|
|
expect(fn).toHaveBeenCalledWith('foo', 'bar');
|
|
});
|
|
|
|
it("scheduled fns can optionally reoccur", function() {
|
|
var scheduler = new j$.DelayedFunctionScheduler(),
|
|
fn = jasmine.createSpy('fn');
|
|
|
|
scheduler.scheduleFunction(fn, 20, [], true);
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
|
|
scheduler.tick(20);
|
|
|
|
expect(fn.calls.count()).toBe(1);
|
|
|
|
scheduler.tick(40);
|
|
|
|
expect(fn.calls.count()).toBe(3);
|
|
|
|
scheduler.tick(21);
|
|
|
|
expect(fn.calls.count()).toBe(4);
|
|
|
|
});
|
|
|
|
it("increments scheduled fns ids unless one is passed", function() {
|
|
var scheduler = new j$.DelayedFunctionScheduler();
|
|
|
|
expect(scheduler.scheduleFunction(function() {
|
|
}, 0)).toBe(1);
|
|
expect(scheduler.scheduleFunction(function() {
|
|
}, 0)).toBe(2);
|
|
expect(scheduler.scheduleFunction(function() {
|
|
}, 0, [], false, 123)).toBe(123);
|
|
expect(scheduler.scheduleFunction(function() {
|
|
}, 0)).toBe(3);
|
|
});
|
|
|
|
it("#removeFunctionWithId removes a previously scheduled function with a given id", function() {
|
|
var scheduler = new j$.DelayedFunctionScheduler(),
|
|
fn = jasmine.createSpy('fn'),
|
|
timeoutKey;
|
|
|
|
timeoutKey = scheduler.scheduleFunction(fn, 0);
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
|
|
scheduler.removeFunctionWithId(timeoutKey);
|
|
|
|
scheduler.tick(0);
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("reset removes scheduled functions", function() {
|
|
var scheduler = new j$.DelayedFunctionScheduler(),
|
|
fn = jasmine.createSpy('fn');
|
|
|
|
scheduler.scheduleFunction(fn, 0);
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
|
|
scheduler.reset();
|
|
|
|
scheduler.tick(0);
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("reset resets the returned ids", function() {
|
|
var scheduler = new j$.DelayedFunctionScheduler();
|
|
expect(scheduler.scheduleFunction(function() { }, 0)).toBe(1);
|
|
expect(scheduler.scheduleFunction(function() { }, 0, [], false, 123)).toBe(123);
|
|
|
|
scheduler.reset();
|
|
expect(scheduler.scheduleFunction(function() { }, 0)).toBe(1);
|
|
expect(scheduler.scheduleFunction(function() { }, 0, [], false, 123)).toBe(123);
|
|
});
|
|
|
|
it("reset resets the current tick time", function() {
|
|
var scheduler = new j$.DelayedFunctionScheduler(),
|
|
fn = jasmine.createSpy('fn');
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
|
|
scheduler.tick(15);
|
|
scheduler.reset();
|
|
|
|
scheduler.scheduleFunction(fn, 20, [], false, 1, 20);
|
|
|
|
scheduler.tick(5);
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("executes recurring functions interleaved with regular functions in the correct order", function() {
|
|
var scheduler = new j$.DelayedFunctionScheduler(),
|
|
fn = jasmine.createSpy('fn'),
|
|
recurringCallCount = 0,
|
|
recurring = jasmine.createSpy('recurring').and.callFake(function() {
|
|
recurringCallCount++;
|
|
if (recurringCallCount < 5) {
|
|
expect(fn).not.toHaveBeenCalled();
|
|
}
|
|
});
|
|
|
|
scheduler.scheduleFunction(recurring, 10, [], true);
|
|
scheduler.scheduleFunction(fn, 50);
|
|
|
|
scheduler.tick(60);
|
|
|
|
expect(recurring).toHaveBeenCalled();
|
|
expect(recurring.calls.count()).toBe(6);
|
|
expect(fn).toHaveBeenCalled();
|
|
});
|
|
|
|
it("schedules a function for later execution during a tick", function () {
|
|
var scheduler = new j$.DelayedFunctionScheduler(),
|
|
fn = jasmine.createSpy('fn'),
|
|
fnDelay = 10;
|
|
|
|
scheduler.scheduleFunction(function () {
|
|
scheduler.scheduleFunction(fn, fnDelay);
|
|
}, 0);
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
|
|
scheduler.tick(fnDelay);
|
|
|
|
expect(fn).toHaveBeenCalled();
|
|
});
|
|
|
|
it("#removeFunctionWithId removes a previously scheduled function with a given id during a tick", function () {
|
|
var scheduler = new j$.DelayedFunctionScheduler(),
|
|
fn = jasmine.createSpy('fn'),
|
|
fnDelay = 10,
|
|
timeoutKey;
|
|
|
|
scheduler.scheduleFunction(function () {
|
|
scheduler.removeFunctionWithId(timeoutKey);
|
|
}, 0);
|
|
timeoutKey = scheduler.scheduleFunction(fn, fnDelay);
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
|
|
scheduler.tick(fnDelay);
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("executes recurring functions interleaved with regular functions and functions scheduled during a tick in the correct order", function () {
|
|
var scheduler = new j$.DelayedFunctionScheduler(),
|
|
fn = jasmine.createSpy('fn'),
|
|
recurringCallCount = 0,
|
|
recurring = jasmine.createSpy('recurring').and.callFake(function() {
|
|
recurringCallCount++;
|
|
if (recurringCallCount < 5) {
|
|
expect(fn).not.toHaveBeenCalled();
|
|
}
|
|
}),
|
|
innerFn = jasmine.createSpy('innerFn').and.callFake(function() {
|
|
expect(recurring.calls.count()).toBe(4);
|
|
expect(fn).not.toHaveBeenCalled();
|
|
}),
|
|
scheduling = jasmine.createSpy('scheduling').and.callFake(function() {
|
|
expect(recurring.calls.count()).toBe(3);
|
|
expect(fn).not.toHaveBeenCalled();
|
|
scheduler.scheduleFunction(innerFn, 10); // 41ms absolute
|
|
});
|
|
|
|
scheduler.scheduleFunction(recurring, 10, [], true);
|
|
scheduler.scheduleFunction(fn, 50);
|
|
scheduler.scheduleFunction(scheduling, 31);
|
|
|
|
scheduler.tick(60);
|
|
|
|
expect(recurring).toHaveBeenCalled();
|
|
expect(recurring.calls.count()).toBe(6);
|
|
expect(fn).toHaveBeenCalled();
|
|
expect(scheduling).toHaveBeenCalled();
|
|
expect(innerFn).toHaveBeenCalled();
|
|
});
|
|
|
|
});
|
|
|