explodorama / files / src / ui / fps.ts
fps.ts
0b63d158 2026-10-25 Pavel Safronov | 1 | import { getCanvasPixelRatio, mainCanvas } from 'littlejsengine' |
| 2 | ||
| 3 | /** | |
| 4 | * A frame-rate meter in the bottom left corner (user request, 2026-10-24: the game crawled in | |
| 5 | * Android Chrome). Shown with `?fps` in the address (in `npm run dev` too until the user turned it | |
| 6 | * off there, same day). It says the frames a second, the slowest frame of the last half second, | |
| 7 | * how long the game's own update and drawing took per frame (the rest of a slow frame is the | |
| 8 | * browser: style, layout, painting, the GPU), and the canvas's size in pixels with the screen's | |
| 9 | * pixel ratio. | |
| 10 | */ | |
| 11 | const SHOW = new URLSearchParams(location.search).has('fps') | |
| 12 | const EVERY_MS = 500 | |
| 13 | ||
| 14 | let meter: HTMLElement | null = null | |
| 15 | let frames = 0 | |
| 16 | let since = performance.now() | |
| 17 | let lastFrame = since | |
| 18 | let slowest = 0 | |
| 19 | let gameMs = 0 | |
| 20 | ||
| 21 | /** Wraps an engine callback, so the time spent in it counts as the game's own. */ | |
| 22 | export function timed(callback: () => void): () => void { | |
| 23 | if (!SHOW) return callback | |
| 24 | return () => { | |
| 25 | const start = performance.now() | |
| 26 | callback() | |
| 27 | gameMs += performance.now() - start | |
| 28 | } | |
| 29 | } | |
| 30 | ||
| 31 | /** Called once a frame, after drawing. */ | |
| 32 | export function countFrame(): void { | |
| 33 | if (!SHOW) return | |
| 34 | if (!meter) { | |
| 35 | meter = document.createElement('div') | |
| 36 | meter.id = 'fps' | |
| 37 | document.body.append(meter) | |
| 38 | } | |
| 39 | const now = performance.now() | |
| 40 | slowest = Math.max(slowest, now - lastFrame) | |
| 41 | lastFrame = now | |
| 42 | frames++ | |
| 43 | if (now - since < EVERY_MS) return | |
| 44 | const seconds = (now - since) / 1000 | |
| 45 | meter.textContent = | |
| 46 | `${Math.round(frames / seconds)} fps · slowest ${Math.round(slowest)} ms · game ${(gameMs / frames).toFixed(1)} ms\n` + | |
| 47 | `${mainCanvas.width}×${mainCanvas.height} px · ×${getCanvasPixelRatio().toFixed(2)}` | |
| 48 | frames = 0 | |
| 49 | slowest = 0 | |
| 50 | gameMs = 0 | |
| 51 | since = now | |
| 52 | } |