Files
gods-eye-view/src/renderGovernor.js
T

147 lines
5.2 KiB
JavaScript

/**
* Idle render governor — the wave-2 flagship of the 2026-08-05 perf
* investigation.
*
* The problem: Cesium's default render loop repaints every vsync forever, so
* the app burned ~60% GPU + ~54% of a core with ZERO layers enabled and a
* parked camera. The fix: flip the scene into Cesium's `requestRenderMode`
* whenever nothing animates per frame, and return to the continuous loop the
* moment something does.
*
* Architecture — a binary mode driven by ref-counted holds:
*
* - **Continuous mode** (`requestRenderMode = false`, today's behavior)
* while ANY hold is registered. Every per-frame animator — fleet
* interpolation, traffic sim, satellite motion, tracked-entity follow,
* style crossfades, CCTV projection — registers a hold for exactly the
* lifetime of its scene-loop listener or animation. While one is active,
* behavior is byte-identical to pre-governor main: the locked
* interpolation/tracking invariants are preserved by construction.
* - **Idle mode** (`requestRenderMode = true`) when zero holds. Cesium
* auto-renders on camera input and tile loads; every other scene mutation
* must call `governorRequestRender()` for its one frame. Discrete
* mutators (layer poll ticks, slider writes, annotation changes) route
* through that.
*
* Holds are identity-keyed (a Set of owner ids), NOT a counter — a module
* that double-holds or double-releases cannot corrupt the mode. Owners are
* short stable strings ('flights', 'traffic', 'style-anim', …) so the
* diagnostics read like a story.
*
* The governor is O(1) passive: no per-frame work of its own, ever.
*/
let _viewer = null;
let _installed = false;
const _holds = new Set();
/** Debug trail of the most recent one-shot render requests (idle mode only). */
const _recentRequests = [];
const RECENT_REQUEST_CAP = 16;
function applyMode() {
if (!_installed || !_viewer?.scene) return;
const continuous = _holds.size > 0;
const scene = _viewer.scene;
if (scene.requestRenderMode === !continuous) return;
scene.requestRenderMode = !continuous;
if (!continuous) {
// Entering idle: render one settling frame so anything the last
// continuous frame mutated is on screen before the loop stops.
scene.requestRender?.();
}
}
/**
* Install the governor on the viewer. Idempotent. Before install,
* hold/release still record into the holds set (and apply at install time);
* requests are safe no-ops — so modules can call all three unconditionally
* in tests without a viewer.
* @param {Cesium.Viewer} viewer
* @returns {void}
*/
export function installRenderGovernor(viewer) {
if (!viewer?.scene) throw new TypeError('installRenderGovernor requires a Cesium viewer');
_viewer = viewer;
_installed = true;
// Never let Cesium re-render on simulation-time deltas behind our back —
// idle means idle. All re-renders are camera/tiles (Cesium-native) or
// explicit requests.
viewer.scene.maximumRenderTimeChange = Infinity;
applyMode();
}
/**
* Register a continuous-render hold. Idempotent per owner.
* Call where the owner's per-frame work BEGINS (scene listener installed,
* animation starts, tracking begins).
* @param {string} ownerId Short stable id, e.g. 'flights', 'traffic'.
* @returns {void}
*/
export function holdContinuousRender(ownerId) {
if (!ownerId) return;
_holds.add(ownerId);
applyMode();
}
/**
* Release a hold. Safe when never held.
* Call where the owner's per-frame work ENDS (listener removed, animation
* settled, tracking stopped, layer disabled).
* @param {string} ownerId
* @returns {void}
*/
export function releaseContinuousRender(ownerId) {
if (!ownerId) return;
_holds.delete(ownerId);
applyMode();
}
/**
* One-shot render request for a discrete scene mutation (layer tick, slider
* write, annotation change). Always forwards to scene.requestRender() — in
* continuous mode that is a harmless flag set (and forwarding closes the
* request-then-last-release race); only idle-mode requests are recorded in
* diagnostics. Cheap enough to call unconditionally after any mutation.
* @param {string} [reason] For diagnostics only.
* @returns {void}
*/
export function governorRequestRender(reason = 'unspecified') {
if (!_installed || !_viewer?.scene) return;
if (_holds.size === 0) {
_recentRequests.push({ reason, at: Date.now() });
if (_recentRequests.length > RECENT_REQUEST_CAP) _recentRequests.shift();
}
_viewer.scene.requestRender?.();
}
/**
* @returns {{installed: boolean, mode: 'continuous'|'idle', holds: string[],
* recentRequests: Array<{reason: string, at: number}>}}
*/
export function getRenderGovernorDiagnostics() {
return {
installed: _installed,
mode: _holds.size > 0 ? 'continuous' : 'idle',
holds: [..._holds].sort(),
recentRequests: [..._recentRequests],
};
}
/** Release the installed viewer after its animation owners have stopped. */
export function uninstallRenderGovernor(viewer) {
if (_viewer !== viewer) return;
_viewer = null;
_installed = false;
_holds.clear();
_recentRequests.length = 0;
}
/** Test seam: reset module state between unit tests. */
export function _resetRenderGovernorForTest() {
_viewer = null;
_installed = false;
_holds.clear();
_recentRequests.length = 0;
}