"use client"; import { useEffect, useReducer, useSyncExternalStore } from "react"; import { engine } from "../engine/SessionEngine"; /** Re-renders the component on (throttled) engine state changes. */ export function useEngineVersion(): number { return useSyncExternalStore(engine.subscribe, engine.getVersion, engine.getVersion); } /** * Re-renders the component on EVERY animation frame, for smooth sub-throttle * motion the 180 ms uiVersion bump is too coarse for: moving car markers, live * telemetry numerals. The playback clock is driven once by useEngineClock; this * only re-reads `engine.t` per frame (returned for convenience). Mount it on the * specific live sub-tree that needs 60 fps, not the whole cockpit. */ export function useEngineFrame(active = true): number { const [, tick] = useReducer((n: number) => (n + 1) | 0, 0); useEffect(() => { if (!active) return; let raf = 0; const loop = () => { tick(); raf = requestAnimationFrame(loop); }; raf = requestAnimationFrame(loop); return () => cancelAnimationFrame(raf); }, [active]); return engine.t; } /** * Drives the playback clock. Mount ONCE at the session root; components then read * `engine.t` (throttled) or call the hot readers with `performance.now()` for 60fps canvas. * `frame()` is idempotent within a frame, so extra callers are harmless. */ export function useEngineClock(active = true): void { useEffect(() => { if (!active) return; let raf = 0; const tick = () => { engine.frame(performance.now()); raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [active]); } export { engine };