export interface Clock { now(): number; setTimer(callback: () => void, delayMs: number): unknown; clearTimer(timer: unknown): void; } export const systemClock: Clock = { now: () => Date.now(), setTimer: (callback, delayMs) => setTimeout(callback, Math.min(delayMs, 2_147_483_647)), clearTimer: (timer) => clearTimeout(timer as NodeJS.Timeout), }; export class LoopScheduler { private wakeTimer?: unknown; private deadlineTimer?: unknown; private generation = 0; constructor(private readonly clock: Clock) {} arm(wakeAt: number | undefined, deadlineAt: number | undefined, onWake: () => void, onDeadline: () => void): void { this.cancel(); const generation = this.generation; if (wakeAt !== undefined) this.wakeTimer = this.clock.setTimer(() => { if (generation === this.generation) onWake(); }, Math.max(0, wakeAt - this.clock.now())); if (deadlineAt !== undefined) this.deadlineTimer = this.clock.setTimer(() => { if (generation === this.generation) onDeadline(); }, Math.max(0, deadlineAt - this.clock.now())); } cancel(): void { this.generation++; if (this.wakeTimer !== undefined) this.clock.clearTimer(this.wakeTimer); if (this.deadlineTimer !== undefined) this.clock.clearTimer(this.deadlineTimer); this.wakeTimer = undefined; this.deadlineTimer = undefined; } }