68 lines
2.1 KiB
TypeScript
68 lines
2.1 KiB
TypeScript
import { formatError, formatErrorMessage } from "./errors";
|
|
import Input from "./input";
|
|
import { nextFrame } from "./utils";
|
|
|
|
interface FrameMeta {
|
|
fps: number;
|
|
now: number;
|
|
}
|
|
type Awaitable<T> = PromiseLike<T> | T;
|
|
|
|
type Setup<T> = () => Awaitable<T>;
|
|
type Frame<T> = (dt: number, state: T, meta: FrameMeta) => Awaitable<T | void>;
|
|
|
|
export function gameLoop<T>(frame: Frame<T>): RunGame;
|
|
export function gameLoop<T>(setup: Setup<T>, frame: Frame<T>): RunGame;
|
|
export function gameLoop<T>(setupOrFrame: Setup<T> | Frame<T>, frame?: Frame<T>): RunGame {
|
|
return async () => {
|
|
let state: T;
|
|
try {
|
|
if (frame) {
|
|
state = await (setupOrFrame as Setup<T>)();
|
|
} else {
|
|
frame = setupOrFrame as Frame<T>;
|
|
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;
|
|
}
|
|
}
|
|
};
|