import { JobScheduler } from "../../Source/Cesium.js"; import { JobType } from "../../Source/Cesium.js"; describe("Scene/JobScheduler", function () { var originalGetTimestamp; beforeAll(function () { originalGetTimestamp = JobScheduler.getTimestamp; var time = 0.0; JobScheduler.getTimestamp = function () { return time++; }; }); afterAll(function () { JobScheduler.getTimestamp = originalGetTimestamp; }); /////////////////////////////////////////////////////////////////////////// var MockJob = function () { this.executed = false; }; MockJob.prototype.execute = function () { this.executed = true; }; /////////////////////////////////////////////////////////////////////////// it("constructs with defaults", function () { var js = new JobScheduler(); expect(js.totalBudget).toEqual(50.0); var budgets = js._budgets; expect(budgets.length).toEqual(JobType.NUMBER_OF_JOB_TYPES); expect(budgets[JobType.TEXTURE].total).toEqual(10.0); expect(budgets[JobType.PROGRAM].total).toEqual(10.0); expect(budgets[JobType.BUFFER].total).toEqual(30.0); }); it("executes a job", function () { var js = new JobScheduler([ 2.0, // JobType.TEXTURE 0.0, // JobType.PROGRAM 0.0, ]); // JobType.BUFFER var job = new MockJob(); var executed = js.execute(job, JobType.TEXTURE); expect(executed).toEqual(true); expect(job.executed).toEqual(true); expect(js._totalUsedThisFrame).toEqual(1.0); expect(js._budgets[JobType.TEXTURE].total).toEqual(2.0); expect(js._budgets[JobType.TEXTURE].usedThisFrame).toEqual(1.0); }); it("disableThisFrame does not execute a job", function () { var js = new JobScheduler([2.0, 0.0, 0.0]); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); js.disableThisFrame(); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false); }); it("executes different job types", function () { var js = new JobScheduler([1.0, 1.0, 1.0]); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(true); expect(js._totalUsedThisFrame).toEqual(3.0); var budgets = js._budgets; expect(budgets[JobType.TEXTURE].usedThisFrame).toEqual(1.0); expect(budgets[JobType.PROGRAM].usedThisFrame).toEqual(1.0); expect(budgets[JobType.BUFFER].usedThisFrame).toEqual(1.0); }); it("executes a second job", function () { var js = new JobScheduler([2.0, 0.0, 0.0]); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); expect(js._totalUsedThisFrame).toEqual(2.0); expect(js._budgets[JobType.TEXTURE].usedThisFrame).toEqual(2.0); }); it("does not execute second job (exceeds total time)", function () { var js = new JobScheduler([1.0, 0.0, 0.0]); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false); expect(js._budgets[JobType.TEXTURE].starvedThisFrame).toEqual(true); }); it("executes a second job (TEXTURE steals PROGRAM budget)", function () { var js = new JobScheduler([1.0, 1.0, 0.0]); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); expect(js._totalUsedThisFrame).toEqual(2.0); var budgets = js._budgets; expect(budgets[JobType.TEXTURE].usedThisFrame).toEqual(1.0); expect(budgets[JobType.TEXTURE].starvedThisFrame).toEqual(true); expect(budgets[JobType.PROGRAM].usedThisFrame).toEqual(0.0); expect(budgets[JobType.PROGRAM].stolenFromMeThisFrame).toEqual(1.0); expect(budgets[JobType.PROGRAM].starvedThisFrame).toEqual(false); // There are no budgets left to steal from expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false); expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); // Allowed once per frame expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false); expect(budgets[JobType.PROGRAM].starvedThisFrame).toEqual(true); }); it("does not steal in the same frame", function () { var js = new JobScheduler([1.0, 1.0, 1.0]); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(true); // Exhaust budget for all job types in the first frame expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false); expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false); expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(false); // In this next frame, no job type can steal from another since // they all exhausted their budgets in the previous frame js.resetBudgets(); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false); expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false); expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(true); expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(false); }); it("does not steal from starving job types over multiple frames", function () { var js = new JobScheduler([1.0, 1.0, 0.0]); // Exhaust in first frame expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); // Stolen from PROGRAM expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false); js.resetBudgets(); expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false); // Can't steal from TEXTURE expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false); js.resetBudgets(); expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false); // Can't steal from TEXTURE yet js.resetBudgets(); expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); // Can steal from TEXTURE since it wasn't exhausted last frame }); it("Allows progress on all job types once per frame", function () { var js = new JobScheduler([1.0, 1.0, 1.0]); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); // Steal from PROGRAM expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); // Steal from BUFFER expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(false); expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); // Still gets to make progress once this frame expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(false); expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(true); // Still gets to make progress once this frame expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(false); }); it("Long job still allows progress on other job types once per frame", function () { // Job duration is always 1.0 in the tests so shorten budget var js = new JobScheduler([0.5, 0.2, 0.2]); expect(js.execute(new MockJob(), JobType.TEXTURE)).toEqual(true); // Goes over total budget expect(js.execute(new MockJob(), JobType.PROGRAM)).toEqual(true); // Still gets to make progress once this frame expect(js.execute(new MockJob(), JobType.BUFFER)).toEqual(true); // Still gets to make progress once this frame }); it("constructor throws when budgets.length is not JobType.NUMBER_OF_JOB_TYPES", function () { expect(function () { return new JobScheduler([1.0]); }).toThrowDeveloperError(); }); });