65 lines
2.5 KiB
TypeScript
65 lines
2.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
createFrameRateGovernor,
|
|
resetFrameRateGovernor,
|
|
stepFrameRateGovernor,
|
|
} from "./frameRateGovernor";
|
|
|
|
function renderCount(targetFps: 30 | 60, sourceFps: number, durationMs = 1_000): number {
|
|
let state = createFrameRateGovernor(targetFps);
|
|
let renders = 0;
|
|
for (let timestamp = 0; timestamp <= durationMs; timestamp += 1_000 / sourceFps) {
|
|
const step = stepFrameRateGovernor(state, timestamp);
|
|
state = step.state;
|
|
if (step.shouldRender) renders += 1;
|
|
}
|
|
return renders;
|
|
}
|
|
|
|
describe("frame-rate governor", () => {
|
|
it("holds capped modes near their target on high-refresh displays", () => {
|
|
expect(renderCount(30, 144)).toBeGreaterThanOrEqual(30);
|
|
expect(renderCount(30, 144)).toBeLessThanOrEqual(31);
|
|
expect(renderCount(60, 240)).toBeGreaterThanOrEqual(60);
|
|
expect(renderCount(60, 240)).toBeLessThanOrEqual(61);
|
|
});
|
|
|
|
it("renders every source callback in display mode", () => {
|
|
let state = createFrameRateGovernor("display", 4);
|
|
for (const timestamp of [1_000, 1_008, 1_016]) {
|
|
const step = stepFrameRateGovernor(state, timestamp);
|
|
state = step.state;
|
|
expect(step.shouldRender).toBe(true);
|
|
}
|
|
expect(state.elapsedSeconds).toBeCloseTo(4.016);
|
|
});
|
|
|
|
it("skips missed deadlines without emitting a catch-up burst", () => {
|
|
let state = createFrameRateGovernor(60);
|
|
state = stepFrameRateGovernor(state, 0).state;
|
|
const delayed = stepFrameRateGovernor(state, 100);
|
|
expect(delayed.shouldRender).toBe(true);
|
|
expect(delayed.state.nextDeadlineMs).toBeGreaterThan(100);
|
|
const sameTimestamp = stepFrameRateGovernor(delayed.state, 100);
|
|
expect(sameTimestamp.shouldRender).toBe(false);
|
|
});
|
|
|
|
it("preserves elapsed time while resetting the wall-clock baseline", () => {
|
|
let state = createFrameRateGovernor(30, 2);
|
|
state = stepFrameRateGovernor(state, 1_000).state;
|
|
state = stepFrameRateGovernor(state, 1_034).state;
|
|
const reset = resetFrameRateGovernor(state, 60);
|
|
const resumed = stepFrameRateGovernor(reset, 90_000);
|
|
expect(resumed.shouldRender).toBe(true);
|
|
expect(resumed.elapsedSeconds).toBeCloseTo(state.elapsedSeconds);
|
|
});
|
|
|
|
it("never moves elapsed time backwards", () => {
|
|
let state = createFrameRateGovernor(60);
|
|
state = stepFrameRateGovernor(state, 20).state;
|
|
state = stepFrameRateGovernor(state, 40).state;
|
|
const backwards = stepFrameRateGovernor(state, 10);
|
|
expect(backwards.elapsedSeconds).toBe(state.elapsedSeconds);
|
|
});
|
|
});
|