mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
Supersedes #235 by @cora-fresh-labs, ported onto the current component layout with fixes from review. Verified before merge: format, package boundaries, unit suite, build, and the browser tracking suite; a browser harness for the change where one applies; adversarial review applied. Future work (not in this PR): hand-drawn marks do not persist across a reload today, and neither do spoken annotations; there is no storage layer for marks. The natural home is Scenes: a drawing that belongs to a shot and persists per shot, so a scene animation can carry its own marks. Needs an owner. Co-authored-by: Clint Borle <[email protected]>
176 lines
7.1 KiB
JavaScript
176 lines
7.1 KiB
JavaScript
import * as Cesium from 'cesium';
|
|
import { isPointerFree } from '../../data/inputOwnership.js';
|
|
|
|
export function createLifecycle({
|
|
state: layerState,
|
|
services,
|
|
parts,
|
|
source,
|
|
}) {
|
|
const { holdContinuousRender, releaseContinuousRender } = services.render;
|
|
|
|
const methods = {
|
|
init(viewer) {
|
|
layerState._viewer = viewer;
|
|
layerState._enabled = false;
|
|
parts.panel.createMissionPanel();
|
|
parts.overlays.createReplayVehicleOverlay();
|
|
layerState._dataSource = new Cesium.CustomDataSource('rocket-launches');
|
|
layerState._dataSource.show = false;
|
|
viewer.dataSources.add(layerState._dataSource);
|
|
layerState._count = 0;
|
|
layerState._lastUpdate = null;
|
|
layerState._lastError = null;
|
|
layerState._orbitMatches = 0;
|
|
parts.overlays.clearMissionOverlaySources();
|
|
layerState._clickHandler = new Cesium.ScreenSpaceEventHandler(
|
|
viewer.scene.canvas,
|
|
);
|
|
layerState._clickHandler.setInputAction((movement) => {
|
|
// A tool owns the pointer (src/data/inputOwnership.js): yield the click.
|
|
if (!isPointerFree()) return;
|
|
if (!layerState._enabled || !layerState._dataSource?.show) return;
|
|
const entity = viewer.scene
|
|
.drillPick(movement.position, 12)
|
|
.map((picked) => picked?.id)
|
|
.find((candidate) => parts.selection.entityLaunchId(candidate));
|
|
const launchId = parts.selection.entityLaunchId(entity);
|
|
if (!launchId) return;
|
|
parts.selection.setSelectedMission(launchId);
|
|
parts.selection.focusMission(
|
|
layerState._launches.find((launch) => launch.id === launchId),
|
|
);
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
|
layerState._moveHandler = new Cesium.ScreenSpaceEventHandler(
|
|
viewer.scene.canvas,
|
|
);
|
|
layerState._moveHandler.setInputAction((movement) => {
|
|
if (!layerState._enabled || !layerState._dataSource?.show) return;
|
|
const missionEntity = viewer.scene
|
|
.drillPick(movement.endPosition, 12)
|
|
.map((picked) => picked?.id)
|
|
.find((candidate) => parts.selection.entityLaunchId(candidate));
|
|
viewer.scene.canvas.style.cursor = missionEntity ? 'pointer' : '';
|
|
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
|
|
// Apply horizon visibility before Cesium traverses and picks the scene.
|
|
// A postRender write is one frame late and can remain visibly stale when
|
|
// request-on-demand rendering stops after a globe camera move.
|
|
layerState._declutterHandler = viewer.scene.preRender.addEventListener(
|
|
parts.rendering.updateMissionFrame,
|
|
);
|
|
parts.launchPad.initLaunchPadZonePrimitive();
|
|
},
|
|
|
|
async enable() {
|
|
layerState._sourceController.abort();
|
|
layerState._sourceController = new AbortController();
|
|
layerState._enabled = true;
|
|
holdContinuousRender('rocket-launches'); // per-frame animator (perf wave 2)
|
|
layerState._lifecycleToken++;
|
|
layerState._postTleRetryCount = 0;
|
|
try {
|
|
await this._enableBody();
|
|
} catch (error) {
|
|
// Enable is a transaction: a failed dependency must not leave mission
|
|
// UI/datasource visible or the satellite snapshot retained (a retained
|
|
// snapshot makes the next enable skip dependency capture entirely).
|
|
try {
|
|
await this.disable();
|
|
} catch (cleanupError) {
|
|
console.warn('[Missions] enable rollback failed:', cleanupError);
|
|
}
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
async _enableBody() {
|
|
layerState._updateDirty = false;
|
|
if (layerState._dataSource) layerState._dataSource.show = true;
|
|
parts.orbitRendering.syncMissionOrbitPrimitiveVisibility();
|
|
parts.selection.focusFullGlobe(layerState._viewer);
|
|
document.getElementById('cockpit-context')?.setAttribute('hidden', '');
|
|
if (layerState._selectedLaunchId)
|
|
parts.selection.setSelectedMission(
|
|
layerState._selectedLaunchId,
|
|
layerState._explicitSelection,
|
|
);
|
|
else parts.selection.setSelectedMission(null, false);
|
|
await parts.ingestion.captureSatelliteDependency();
|
|
},
|
|
|
|
async disable() {
|
|
layerState._sourceController.abort();
|
|
layerState._enabled = false;
|
|
releaseContinuousRender('rocket-launches');
|
|
layerState._lifecycleToken++;
|
|
layerState._updateDirty = false;
|
|
parts.ingestion.clearPostTleRetry();
|
|
parts.panel.clearMissionRosterHover();
|
|
parts.replay.stopMissionReplay();
|
|
parts.selection.stopMissionZoomAnchor();
|
|
if (layerState._dataSource) layerState._dataSource.show = false;
|
|
parts.orbitRendering.syncMissionOrbitPrimitiveVisibility();
|
|
if (layerState._viewer?.scene?.canvas)
|
|
layerState._viewer.scene.canvas.style.cursor = '';
|
|
parts.launchPad.hideLaunchPadZone();
|
|
parts.overlays.hideReplayVehicleOverlay();
|
|
layerState._selectedLaunchId = null;
|
|
parts.overlays.clearMissionOverlaySources();
|
|
await parts.ingestion.restoreSatelliteDependency();
|
|
parts.panel.renderMissionPanel();
|
|
},
|
|
|
|
async destroy(viewer) {
|
|
layerState._sourceController.abort();
|
|
releaseContinuousRender('rocket-launches'); // direct-destroy path (perf wave 2 fix)
|
|
layerState._enabled = false;
|
|
layerState._lifecycleToken++;
|
|
layerState._updateDirty = false;
|
|
await parts.ingestion.restoreSatelliteDependency();
|
|
parts.panel.clearMissionRosterHover();
|
|
parts.replay.stopMissionReplay();
|
|
parts.selection.stopMissionZoomAnchor();
|
|
parts.ingestion.clearPostTleRetry();
|
|
if (layerState._clickHandler) layerState._clickHandler.destroy();
|
|
layerState._clickHandler = null;
|
|
if (layerState._moveHandler) layerState._moveHandler.destroy();
|
|
layerState._moveHandler = null;
|
|
if (layerState._declutterHandler) layerState._declutterHandler();
|
|
layerState._declutterHandler = null;
|
|
parts.launchPad.destroyLaunchPadZonePrimitive();
|
|
parts.orbitRendering.removeMissionOrbitPrimitives();
|
|
parts.overlays.clearMissionOverlaySources();
|
|
parts.overlays.destroyReplayVehicleOverlay();
|
|
layerState._launches = [];
|
|
layerState._missionOverlayRecords.clear();
|
|
layerState._animationStarts.clear();
|
|
layerState._satelliteTelemetry.clear();
|
|
layerState._replayTracks.clear();
|
|
layerState._missionPanel?.remove();
|
|
layerState._missionPanel = null;
|
|
if (layerState._missionRoster) {
|
|
layerState._missionRoster.onclick = null;
|
|
}
|
|
layerState._missionRosterPreviewOwnership = null;
|
|
layerState._missionRoster = null;
|
|
layerState._viewer = null;
|
|
if (layerState._dataSource)
|
|
viewer.dataSources.remove(layerState._dataSource, true);
|
|
layerState._dataSource = null;
|
|
layerState._count = 0;
|
|
layerState._orbitMatches = 0;
|
|
layerState._lastUpdate = null;
|
|
layerState._activeTleText = null;
|
|
layerState._activeTlePromise = null;
|
|
layerState._activeTlePromiseToken = 0;
|
|
layerState._renderedTleText = null;
|
|
layerState._postTleRetryCount = 0;
|
|
layerState._satelliteStateBeforeMission = null;
|
|
layerState._satelliteActivationPromise = null;
|
|
layerState._dataManager = null;
|
|
},
|
|
};
|
|
|
|
return { methods };
|
|
}
|