import { type Application, UPDATE_PRIORITY } from 'pixi.js'; /** * Drives the renderer from changes instead of from the clock. * * An idle editor at 60 fps costs ~75 % of a core, and ~64 of those points are the drawing alone – * a picture nobody asked to change. The ticker keeps running (~3 points): the clock, a running * world and the panels still get their frame, only `app.render()` is skipped while nothing asked * for it. * * What wakes it is deliberately not a strict dirty flag. Every pointer and keyboard event, every * `Widget.invalidate()`/`resize()`, every `EditorState.emit()` and every texture or font arriving * opens a short trailing window rather than requesting exactly one frame. A repaint that lands a * few milliseconds after its cause – a microtask, a texture upload, a font resolving – is drawn * without anyone having had to declare it, which is the failure this design refuses to have: * a missing repaint is invisible to the check scripts, because `pixelAt` re-renders on demand. */ /** How long one wake-up keeps drawing. Long enough for the late repaint, short enough to idle. */ const WAKE_MS = 200; let deadline = 0; let rendered = 0; /** Draw, and keep drawing for a moment. Cheap enough to call from every event. */ export function requestRender(): void { deadline = performance.now() + WAKE_MS; } /** Whether the renderer is currently awake. */ export function isRendering(): boolean { return performance.now() <= deadline; } /** Frames actually drawn since the loop was installed – the fps readout and the checks read it. */ export function renderCount(): number { return rendered; } /** * Takes the renderer off the ticker and drives it from `requestRender()` instead. Added at the * ticker's lowest priority, so everything that may request a frame has run for this tick already. */ export function initRenderLoop(app: Application): void { // `TickerPlugin` registers `Application.prototype.render` bound to the app; same pair to remove. app.ticker.remove(app.render, app); app.ticker.add( () => { if (!isRendering()) return; rendered += 1; app.render(); }, undefined, UPDATE_PRIORITY.LOW, ); requestRender(); } /** Tests only: back to a cold loop. */ export function resetRenderLoop(): void { deadline = 0; rendered = 0; }