Files
gods-eye-view/src/layers/cctv/state.js
T
Sameh Khamis 93942d0dbe Show only the CCTV cameras in view, thinned to one per ground cell when zoomed out
The CCTV layer kept every loaded camera live: each view change touched every
icon, every notification rebuilt every camera's state, and the picker listed
the whole catalog. With the default 4,000 cameras, a continental drag ran at
152-179 fps with 18-24 frames over 30 ms (worst 112 ms). It now runs at
235 fps with none. The layer now:

- shows icons only on or near the screen, and when zoomed out keeps one camera
  per world-anchored cell (healthy feeds first), so panning keeps the same
  cameras;
- grounds and draws coverage only for the cameras in view, the active camera
  and its coverage neighbours, retrying a camera at most twice per view regime;
- reruns the view pass while the camera moves, paced by its own cost, and
  projects with one matrix per pass instead of a Cesium call per camera;
- keeps the billboard buffer-usage hint fixed, so showing or hiding a few
  icons no longer rewrites the vertex data of every billboard;
- lists the cameras in view (active camera first) in the picker, closing with
  the count of cameras outside the view, and builds the full camera list in
  the UI state only when it is read.
2026-10-29 21:31:59 -07:00

266 lines
7.7 KiB
JavaScript

import * as Cesium from 'cesium';
export function createState({ services }) {
const {
clearOverlaySource,
hitTestWorldOverlay,
setOverlayEntries,
setOverlaySourceVisible,
} = services.overlays;
const layerState = {};
// ---------------------------------------------------------------------------
// Module-scoped mutable state
// ---------------------------------------------------------------------------
layerState._viewer = null;
layerState._billboards = null;
layerState._records = [];
layerState._recordById = new Map();
layerState._coverageEntities = [];
layerState._projectionEntities = [];
layerState._enabled = false;
layerState._activeCameraId = null;
layerState._coverageMode = 'on';
// 'off' | 'on' (wireframes) | 'viewshed' (color-coded volumes)
layerState._showProjection = true;
layerState._autoHop = false;
// An explicit empty-space deselect keeps AUTO HOP configured but prevents its
// timer from silently choosing a replacement. A later explicit activation or
// AUTO HOP toggle-on releases the hold.
layerState._autoHopSuspended = false;
layerState._autoHopSec = 18;
layerState._lastHopAt = 0;
layerState._lastViewContext = '';
layerState._clickHandler = null;
layerState._count = 0;
layerState._lastUpdate = null;
layerState._lastHealthSyncAt = 0;
layerState._lastError = null;
layerState._healthById = new Map();
layerState._calibrationById = new Map();
layerState._listeners = new Set();
layerState._projectionRaf = 0;
layerState._removeFocusAppearListener = null;
layerState._lastFocusStyleAt = 0;
/** Icons whose animated emphasis remains outside the 1.0 deadband. */
layerState._activeFocusStyleCount = 0;
layerState._scratchFocusScreen = new Cesium.Cartesian2();
layerState._scratchViewScreen = new Cesium.Cartesian2();
layerState._scratchViewProjection = new Cesium.Matrix4();
// Counts view passes; a record shown by the latest pass carries its number.
layerState._viewPassId = 0;
// Records whose icons the last view pass showed (see refreshHorizonCulling):
// geometry refinement, the tiles-ready completion pass and ambient cards
// work over these, not the whole catalog.
layerState._viewRecords = [];
// Coverage ids refreshCoverageStyles last drew: part of the geometry
// working set even when their icons are thinned out.
layerState._coverageVisibleIds = new Set();
// View pass triggers besides camera settle: an interval while the camera
// moves, and a canvas resize observer.
layerState._viewMotionTimer = 0;
layerState._viewResizeObserver = null;
layerState._viewResizeCanvas = null;
// The camera the moveStart/moveEnd listeners are attached to.
layerState._viewListenerCamera = null;
// A motion pass changed the shown set and the settle pass has not yet told
// subscribers; the camera pose the last view pass ran at.
layerState._viewUnpublished = false;
layerState._viewPose = null;
// { at, ms } — when the last motion pass ran and what it cost.
layerState._viewMotionPass = null;
// Retries the view geometry a drain left unresolved, once cells warm.
layerState._viewGeometryRetryTimer = 0;
// { key, count } — the active camera's coverage-overlap count, which the
// summary reads on every notification.
layerState._neighborCountCache = null;
// { view, active, list } — the camera picker's index uiState publishes,
// rebuilt only when the view pass or the active camera changes.
layerState._cameraIndexCache = null;
// Staggered geometry-load queue state (see startGeometryLoadQueue).
layerState._geoQueue = [];
layerState._geoQueueTimer = 0;
layerState._geoLoading = false;
layerState._geoLoadTotal = 0;
layerState._geoLoadDone = 0;
layerState._geoProgressNotifier = null;
// One-shot completion latch for shared floor resolution: the enable-time queue
// can drain while DEM cells or 3D tiles are still loading. The first update()
// tick that sees projectionTilesReady() re-enqueues unresolved records ONCE;
// shared mesh cells remain one-shot and idle ticks stay sample-free. Reset by
// startGeometryLoadQueue so each enable-time drain gets its own completion pass.
layerState._tilesReadyReenqueued = false;
// Calibration ADJUST mode (viewshed/gizmo design §3c): while true, the active
// camera renders the direct-manipulation gizmo. Reset on layer disable.
layerState._calibrationMode = false;
layerState._gizmo = null;
layerState._lastTransientNotifyAt = 0;
// Cached handle on the active Google Photorealistic 3D Tileset, discovered
// lazily from scene.primitives. Shared mesh-floor sampling is gated on its
// tilesLoaded flag so a coarse-LOD miss is never baked in. Cleared when the
// tileset is destroyed / the layer tears down.
layerState._activeTileset = null;
// Task 5: last surface regime the record geometry was recomputed for. The
// map-stack change listener compares the CURRENT regime (derived live from
// scene.globe.show) against this so bing→osm switches (same 'terrain-globe'
// regime) don't trigger a pointless full-catalog rewrite.
layerState._lastAppliedRegime = null;
// Task 5: window listener handle for the 'gev:map-stack-changed' CustomEvent
// main.js dispatches from MapStackController's onChange (removed in destroy).
layerState._mapStackListener = null;
// Field-test fix (2026-07-06): camera.moveEnd handle for the horizon-culling
// pass (removed in destroy). Event-driven only — never a per-frame loop, so
// the zero-steady-state-work invariant holds.
layerState._horizonCullListener = null;
// Ambient card tier state (2026-07-29 design). The card set is rebuilt only
// on moveEnd/enable/activation (refreshAmbientCards); frame slots are STABLE
// objects shared with the overlay host so landed frames appear without an
// entry rebuild.
layerState._cardIds = new Set();
/** @type {Map<string,{misses:number,since:number}>} */
layerState._cardGraceState = new Map();
/** @type {Map<string,{frame:*, stamp:number, failCount:number, lastAttemptAt:number}>} */
layerState._cardFrameSlots = new Map();
layerState._cardFetchTimer = 0;
/** In-flight card-frame fetch count (burst allows up to 4, steady is 1). */
layerState._cardFetchInFlightCount = 0;
/** @type {Set<HTMLImageElement>} in-flight fetches, detached on teardown. */
layerState._cardFetchImages = new Set();
/** @type {Set<string>} camera ids with an in-flight fetch (no double-fetch). */
layerState._cardFetchPendingIds = new Set();
layerState._cardFetchCount = 0;
layerState._cardLastFetchAt = 0;
layerState._cardMinFetchSpacingMs = null;
/** Pacer mode telemetry: 'burst' during cold fill, 'steady' after. */
layerState._cardFetchMode = 'steady';
layerState.DEFAULT_CCTV_OVERLAY_HOST = Object.freeze({
clearSource: clearOverlaySource,
hitTest: hitTestWorldOverlay,
setEntries: setOverlayEntries,
setVisible: setOverlaySourceVisible,
});
layerState._cctvOverlayHost = layerState.DEFAULT_CCTV_OVERLAY_HOST;
layerState._projectionOverlayOwnerId = null;
/**
* Product presentation option. Shipped behavior keeps the active camera's
* thumbnail absent because its monitor plane is the active representation.
*/
layerState._activeCameraCardEnabled = false;
/** Camera id currently holding the hover-summoned pinned card (or null). */
layerState._hoverCardId = null;
layerState._hoverReleaseTimer = 0;
layerState._hoverLastPickAt = 0;
// True between camera.moveStart and moveEnd — hover picking pauses while the
// camera is in motion (picks during a flight would fight the reselection).
layerState._cameraMoving = false;
layerState._moveStartListener = null;
layerState._sourceAbort = null;
return layerState;
}