mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
refactor(cctv): separate camera source and scene components
This commit is contained in:
1 parent
ca5f5fff93
commit
519bc6c362
32 files changed
+6873
-4835
No files matched your search
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
CCTV_CARD_FETCH_BURST_LIMIT,
|
||||
frameFetchDue,
|
||||
} from '../../data/cctvCards.js';
|
||||
import { staticFrameRefreshMs } from '../../data/cctvLod.js';
|
||||
import { HOVER_PICK_THROTTLE_MS, HOVER_RELEASE_MS } from './policy.js';
|
||||
|
||||
export function createHover({ state: layerState, services, parts, source }) {
|
||||
/**
|
||||
* Throttled MOUSE_MOVE hover pass (owner round 2, item B): pointing at a
|
||||
* camera icon that has no card summons its card immediately. This is
|
||||
* EVENT-DRIVEN picking on a user gesture, not steady-state work — the
|
||||
* ≥120 ms throttle caps it at ~8 scene.pick calls/s while the pointer is
|
||||
* actually moving (a still pointer costs nothing), so it can never approach
|
||||
* per-frame cost. Skipped while the camera is in motion, while ADJUST mode
|
||||
* owns the pointer (gizmo drags), and while the layer is disabled.
|
||||
* @param {Cesium.Cartesian2} position - Pointer position (CSS px).
|
||||
*/
|
||||
|
||||
function handleHoverMove(position) {
|
||||
if (
|
||||
!layerState._enabled ||
|
||||
layerState._cameraMoving ||
|
||||
layerState._calibrationMode ||
|
||||
!position
|
||||
)
|
||||
return;
|
||||
if (!layerState._viewer || layerState._viewer.isDestroyed()) return;
|
||||
const now = Date.now();
|
||||
if (now - layerState._hoverLastPickAt < HOVER_PICK_THROTTLE_MS) return;
|
||||
layerState._hoverLastPickAt = now;
|
||||
let picked = null;
|
||||
try {
|
||||
picked = layerState._viewer.scene.pick(position);
|
||||
} catch {
|
||||
picked = null;
|
||||
}
|
||||
const cameraId = parts.selection.extractPickedCameraId(picked);
|
||||
if (cameraId && cameraId === layerState._hoverCardId) {
|
||||
// Still on the hovered camera — keep the card alive.
|
||||
cancelHoverRelease();
|
||||
return;
|
||||
}
|
||||
const record = cameraId ? layerState._recordById.get(cameraId) : null;
|
||||
// Hovering the active camera or a camera that already has a card is a
|
||||
// no-op; video feeds stay icon-only until activated (ambient tier is
|
||||
// stills-only — same rule as the LOD selection).
|
||||
const eligible =
|
||||
!!record &&
|
||||
cameraId !== layerState._activeCameraId &&
|
||||
!layerState._cardIds.has(cameraId) &&
|
||||
!parts.model.isVideoFeedType(
|
||||
parts.model.normalizeFeedType(record.camera.feedType),
|
||||
);
|
||||
if (eligible) {
|
||||
cancelHoverRelease();
|
||||
layerState._hoverCardId = cameraId;
|
||||
// Immediacy: the pinned entry publishes now — chrome may paint before
|
||||
// the first frame arrives (documented exception in cctvCards.js).
|
||||
parts.cards.pushAmbientCardEntries();
|
||||
hoverFetchCardFrame(record);
|
||||
} else if (layerState._hoverCardId) {
|
||||
// Pointer left the hovered icon: linger ~1 s, then release.
|
||||
scheduleHoverRelease();
|
||||
}
|
||||
}
|
||||
|
||||
/** Cancels a pending hover-card release. */
|
||||
|
||||
function cancelHoverRelease() {
|
||||
if (layerState._hoverReleaseTimer) {
|
||||
clearTimeout(layerState._hoverReleaseTimer);
|
||||
layerState._hoverReleaseTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the hover card's release ~1 s after unhover. On release the pin
|
||||
* drops; the card stays only if the LOD selection has adopted the camera
|
||||
* (it is then a normal budgeted entry in `_cardIds`).
|
||||
*/
|
||||
|
||||
function scheduleHoverRelease() {
|
||||
if (layerState._hoverReleaseTimer) return;
|
||||
layerState._hoverReleaseTimer = setTimeout(() => {
|
||||
layerState._hoverReleaseTimer = 0;
|
||||
layerState._hoverCardId = null;
|
||||
parts.cards.pushAmbientCardEntries();
|
||||
}, HOVER_RELEASE_MS);
|
||||
}
|
||||
|
||||
/** Clears the hover-card state (teardown / activation of the hovered camera). */
|
||||
|
||||
function clearHoverCard() {
|
||||
cancelHoverRelease();
|
||||
layerState._hoverCardId = null;
|
||||
layerState._hoverLastPickAt = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fast-tracked frame fetch for the hover-summoned card: launches
|
||||
* immediately, bypassing the pacer's launch-spacing gate (a single
|
||||
* user-gesture fetch is fine even during the geometry drain), but still
|
||||
* respecting the per-camera failure backoff / freshness check
|
||||
* (frameFetchDue), the no-double-fetch pending set, and the burst in-flight
|
||||
* cap of 4.
|
||||
* @param {Object} record - The hovered camera's record.
|
||||
*/
|
||||
|
||||
function hoverFetchCardFrame(record) {
|
||||
const cameraId = record.camera.id;
|
||||
if (layerState._cardFetchPendingIds.has(cameraId)) return;
|
||||
if (layerState._cardFetchInFlightCount >= CCTV_CARD_FETCH_BURST_LIMIT)
|
||||
return;
|
||||
const slot = parts.cards.ensureCardFrameSlot(cameraId);
|
||||
const refreshMs = staticFrameRefreshMs(record.camera);
|
||||
if (!frameFetchDue(slot, refreshMs, Date.now())) return;
|
||||
parts.cards.fetchCardFrame(record, slot, refreshMs, { userGesture: true });
|
||||
}
|
||||
return {
|
||||
handleHoverMove,
|
||||
cancelHoverRelease,
|
||||
scheduleHoverRelease,
|
||||
clearHoverCard,
|
||||
hoverFetchCardFrame,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user