import { formatError, formatErrorMessage } from "./errors"; import Input from "./input"; import { nextFrame } from "./utils"; interface FrameMeta { fps: number; now: number; } type Awaitable = PromiseLike | T; type Setup = () => Awaitable; type Frame = (dt: number, state: T, meta: FrameMeta) => Awaitable; export function gameLoop(frame: Frame): RunGame; export function gameLoop(setup: Setup, frame: Frame): RunGame; export function gameLoop(setupOrFrame: Setup | Frame, frame?: Frame): RunGame { return async () => { let state: T; try { if (frame) { state = await (setupOrFrame as Setup)(); } else { frame = setupOrFrame as Frame; state = {} as T; } } catch (e) { console.error(formatError(e, 'Error in game setup')); alert(formatErrorMessage(e)); return; } try { let prevFrame = performance.now(); let fpsCounter = 0; let fpsTimer = 0; const meta: FrameMeta = { fps: 0, now: 0 }; while (true) { await nextFrame(); Input.updateKeys(); const now = performance.now(); const dt = (now - prevFrame) / 1000; if (dt < 1) { // skip long pause to avoid blowing up values meta.now += dt; const newState = await frame(dt, state, meta); if (newState) { state = newState; } } fpsCounter += 1; if (now - fpsTimer > 500) { meta.fps = fpsCounter * 2; fpsCounter = 0; fpsTimer = now; } prevFrame = performance.now(); } } catch (e) { console.error(formatError(e, 'Error in game loop')); alert(formatErrorMessage(e)); return; } } };