mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-09 21:02:19 +00:00
Scope world overlays to each scene attachment
Move the world overlay into scene.services.overlay. Each attachment owns its sources, collision domains, paint lanes, canvases, observers, timers and accessible action subscribers. Keep both canvases inside the engine container, with detection beside the card root so its screen blend still reaches the scene. Release shared font measurements only after the last host detaches. Resolve retained layer publications against their attached engine, clear the outgoing source before releasing its renderer, and reconnect the shell's action subscription on attachment. Keep destroyed hosts inert and give parked engines fresh services. Bind annotation rectangles and the existing tracked bridge to their engine's overlay. The tracked bridge itself remains page-owned. Preserve Cesium's projection, card placement, arbitration, fades, hit tests, blend order and render demand. Navara keeps the same shared behaviour. No user-visible change is intended. Exercise simultaneous hosts, either teardown, stale handles, reattachment and a retained selected card on the fake and headless Cesium engines. Update QA reads and package ownership for the service. No coupling metric falls or grows: renderer lines remain 0 Cesium and 189 Navara; external Cesium import files 3; viewer lines 891; Navara kind checks 1; engine sniff calls 0; extension reads 3; ground passes 1; SDK loads 3; ScreenSpaceEventHandler files 4; engine-name lines 32. Each host now takes the page's overlay ids from the host it replaces and gives them back to a remaining host when destroyed, and the chrome's static overlay placeholders are gone. The new hosts had left the ids on those empty placeholders, so the voice pointer policy, the id selectors and the QA probes that read #world-overlay-canvas missed the drawn canvas. Checked in the browser, keyless: the overlay baselines on both engines, CCTV v2 (58 checks), military names, and the engine, voice, ground-layer and Ghost Maps switches pass. The transit script fails the same 25 checks as before.
This commit is contained in:
1 parent
26597ad25c
commit
3814221cbc
62 files changed
+3148
-2655
No files matched your search
@@ -283,7 +283,15 @@ one application, and only the globe-bound parts move to the next engine.
|
||||
Cesium implements its render mode directly. Retained layers identify their
|
||||
attached engine, and scene callbacks capture their governor. Teardown clears
|
||||
only that scene's holds; an old service cannot release a replacement's hold.
|
||||
The overlay host and tracked readout still have module-owned state.
|
||||
- **Scene world overlays.** `scene.services.overlay` owns each attachment's
|
||||
label sources, paint lanes, collision domains, canvases, observers, timers
|
||||
and accessible actions. Both canvases live inside the engine container;
|
||||
detection remains outside the card root to preserve its blend. Retained
|
||||
models resolve their attached engine, and the shell reconnects its action
|
||||
subscription after switching. Destroyed services and old lane handles stay
|
||||
inert; a parked engine receives a new host. Shared font measurements live
|
||||
until the last host detaches. The tracked readout remains page-owned and
|
||||
publishes into the attached overlay; its event isolation is separate work.
|
||||
- **The session owns the state.** Catalog, layer models, sources, selection,
|
||||
camera mode and URL state outlive a switch. A layer that is a session model
|
||||
gets `attachRenderer(viewer, { engine })` / `detachRenderer()` instead of being rebuilt,
|
||||
|
||||
@@ -565,6 +565,31 @@ attachment-bound callbacks can capture `renderGovernorFor(engine)` directly.
|
||||
Destroying the scene clears its holds and makes the captured service inert.
|
||||
A parked engine gets a fresh service when reattached.
|
||||
|
||||
## World overlays
|
||||
|
||||
`scene.services.overlay` owns one attachment's world labels and cards. It
|
||||
keeps its sources, collision domains, paint lanes, canvases, frame listeners,
|
||||
observers, timers and accessible action subscribers. `init()` installs its
|
||||
DOM inside `engine.host.container`; the detection blend surface stays beside
|
||||
the card root so it can blend against the scene.
|
||||
|
||||
Publish with `setEntries(sourceId, entries, options)`, `setVisible(sourceId,
|
||||
visible)` and `clearSource(sourceId)`. Read painted rectangles with
|
||||
`getPaintRect(sourceId, entryId)` and register custom painters with
|
||||
`registerPaintLane(laneId, painter, options)`. `subscribeActions(listener)`
|
||||
provides the accessible mirror; `activateAction(owner, key)` rejects an old
|
||||
host's actions. The shell reconnects these operations on every attachment.
|
||||
|
||||
`worldOverlayFor(engine)` resolves the same service for standalone consumers.
|
||||
The module helper functions take the engine first. Retained layer models use
|
||||
`bindOverlayHost` with their attached-engine getter; injected hosts without a
|
||||
`forEngine` resolver keep their own operations. Publications can buffer before
|
||||
`init()`. Destruction is terminal, including before initialization; a parked
|
||||
engine needs fresh scene services. An old paint-lane handle cannot mutate its
|
||||
replacement. Font measurements are shared until the last live host releases
|
||||
them. The tracked-readout bridge remains page-owned and publishes into its
|
||||
attached engine's overlay.
|
||||
|
||||
## Render effects
|
||||
|
||||
The optional `visualStyles`, `bloom` and `contactSonar` capabilities are
|
||||
|
||||
@@ -1705,5 +1705,6 @@
|
||||
"src/app/layers/displayRehearsal.test.mjs",
|
||||
"src/globe/cesium/annotations/worldAnnotationRenderer.js",
|
||||
"src/globe/cesium/annotations/screenAnnotationRenderer.js",
|
||||
"src/globe/navara/windFlow.js"
|
||||
"src/globe/navara/windFlow.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
]
|
||||
@@ -33,7 +33,8 @@
|
||||
"src/layers/infrastructure/index.js",
|
||||
"src/layers/infrastructure/render/display.js",
|
||||
"src/layers/shared/color.js",
|
||||
"src/sources/requestDeadline.js"
|
||||
"src/sources/requestDeadline.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": []
|
||||
},
|
||||
@@ -1225,7 +1226,18 @@
|
||||
"src/ui/wildlifeSightingPlacement.js",
|
||||
"src/overlays/worldOverlayChrome.js",
|
||||
"src/layers/wildlifeSightings/photoText.js",
|
||||
"src/globe/cesium/doubleClick.js"
|
||||
"src/globe/cesium/doubleClick.js",
|
||||
"src/celestialRing.js",
|
||||
"src/celestialRing/ephemeris.js",
|
||||
"src/celestialRing/view.js",
|
||||
"src/data/detectionCohort.js",
|
||||
"src/data/labelArbiter.js",
|
||||
"src/globe/cesium/celestial.js",
|
||||
"src/layers/shared/render/engineView.js",
|
||||
"src/overlays/worldOverlay.js",
|
||||
"src/overlays/worldOverlayDraw.js",
|
||||
"src/overlays/worldOverlayTokens.js",
|
||||
"src/time/clock.js"
|
||||
],
|
||||
"external": ["cesium", "vue", "pinia", "vue-i18n"]
|
||||
},
|
||||
@@ -1604,7 +1616,8 @@
|
||||
"src/globe/cesium/sceneProjection.js",
|
||||
"src/geo/",
|
||||
"src/globe/contract.js",
|
||||
"src/globe/cesium/engineFromViewer.js"
|
||||
"src/globe/cesium/engineFromViewer.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
@@ -1617,7 +1630,8 @@
|
||||
"src/layers/earthquakes/records.js",
|
||||
"src/geo/",
|
||||
"src/layers/shared/color.js",
|
||||
"src/layers/earthquakes/render/display.js"
|
||||
"src/layers/earthquakes/render/display.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": []
|
||||
},
|
||||
@@ -1685,7 +1699,8 @@
|
||||
"src/i18n/locales/zh-Hant.json",
|
||||
"src/geo/",
|
||||
"src/layers/firms/rectangle.js",
|
||||
"src/layers/shared/color.js"
|
||||
"src/layers/shared/color.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": []
|
||||
},
|
||||
@@ -1727,7 +1742,8 @@
|
||||
"src/ui/unitLabels.js",
|
||||
"src/geo/",
|
||||
"src/time/clock.js",
|
||||
"src/layers/shared/color.js"
|
||||
"src/layers/shared/color.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": ["cesium", "satellite.js"]
|
||||
},
|
||||
@@ -1772,7 +1788,8 @@
|
||||
"src/geo/",
|
||||
"src/time/clock.js",
|
||||
"src/globe/contract.js",
|
||||
"src/globe/cesium/engineFromViewer.js"
|
||||
"src/globe/cesium/engineFromViewer.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
@@ -1981,7 +1998,8 @@
|
||||
"src/layers/shared/color.js",
|
||||
"src/geo/",
|
||||
"src/globe/contract.js",
|
||||
"src/globe/cesium/engineFromViewer.js"
|
||||
"src/globe/cesium/engineFromViewer.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": []
|
||||
},
|
||||
@@ -2038,7 +2056,8 @@
|
||||
"src/i18n/locales/zh-CN.json",
|
||||
"src/i18n/locales/zh-Hant.json",
|
||||
"src/layers/shared/color.js",
|
||||
"src/geo/"
|
||||
"src/geo/",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": ["hls.js"]
|
||||
},
|
||||
@@ -2200,7 +2219,8 @@
|
||||
"src/i18n/locales/uk.json",
|
||||
"src/i18n/locales/zh-CN.json",
|
||||
"src/i18n/locales/zh-Hant.json",
|
||||
"src/geo/"
|
||||
"src/geo/",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": []
|
||||
},
|
||||
@@ -2230,7 +2250,8 @@
|
||||
"src/geo/",
|
||||
"src/sources/requestDeadline.js",
|
||||
"src/layers/shared/color.js",
|
||||
"src/layers/submarineCables/render/display.js"
|
||||
"src/layers/submarineCables/render/display.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
@@ -3612,7 +3633,8 @@
|
||||
"src/globe/meshes.js",
|
||||
"src/globe/navara/meshes.js",
|
||||
"src/globe/exclusivePointer.js",
|
||||
"src/globe/cesium/followOffset.js"
|
||||
"src/globe/cesium/followOffset.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": [
|
||||
"hls.js",
|
||||
@@ -4376,7 +4398,8 @@
|
||||
"src/globe/cesium/meshes.js",
|
||||
"src/globe/meshes.js",
|
||||
"src/globe/exclusivePointer.js",
|
||||
"src/globe/cesium/followOffset.js"
|
||||
"src/globe/cesium/followOffset.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": [
|
||||
"hls.js",
|
||||
@@ -4742,7 +4765,8 @@
|
||||
"src/ui/unitLabels.js",
|
||||
"src/geo/",
|
||||
"src/layers/directions/render/display.js",
|
||||
"src/layers/directions/render/style.js"
|
||||
"src/layers/directions/render/style.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": []
|
||||
},
|
||||
@@ -5145,7 +5169,8 @@
|
||||
"src/globe/cesium/meshes.js",
|
||||
"src/globe/meshes.js",
|
||||
"src/globe/exclusivePointer.js",
|
||||
"src/data/selectionDispatcher.js"
|
||||
"src/data/selectionDispatcher.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": ["cesium", "egm96-universal", "pbf"]
|
||||
},
|
||||
@@ -5199,7 +5224,8 @@
|
||||
"src/i18n/locales/zh-Hant.json",
|
||||
"src/layers/shared/color.js",
|
||||
"src/geo/",
|
||||
"src/globe/contract.js"
|
||||
"src/globe/contract.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": ["cesium", "pbf"]
|
||||
},
|
||||
@@ -5265,7 +5291,8 @@
|
||||
"src/i18n/locales/zh-CN.json",
|
||||
"src/i18n/locales/zh-Hant.json",
|
||||
"src/layers/shared/color.js",
|
||||
"src/layers/perimeters/render/display.js"
|
||||
"src/layers/perimeters/render/display.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
@@ -5378,7 +5405,8 @@
|
||||
"src/layers/streetLevel/view.js",
|
||||
"src/data/geoImportRecords.js",
|
||||
"src/data/geoImportKml.js",
|
||||
"src/data/geoImportDisplay.js"
|
||||
"src/data/geoImportDisplay.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
@@ -5426,7 +5454,8 @@
|
||||
"src/i18n/locales/zh-CN.json",
|
||||
"src/i18n/locales/zh-Hant.json",
|
||||
"src/layers/shared/color.js",
|
||||
"src/layers/droneAirspace/render/display.js"
|
||||
"src/layers/droneAirspace/render/display.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
@@ -5631,7 +5660,8 @@
|
||||
"src/ui/statusText.js",
|
||||
"src/ui/layerNames.js",
|
||||
"src/layers/shared/color.js",
|
||||
"src/layers/airQuality/render/display.js"
|
||||
"src/layers/airQuality/render/display.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
@@ -5710,7 +5740,8 @@
|
||||
"src/globe/cesium/credits.js",
|
||||
"src/layers/wildlifeSightings/display.js",
|
||||
"src/data/spriteOrder.js",
|
||||
"src/layers/shared/color.js"
|
||||
"src/layers/shared/color.js",
|
||||
"src/overlays/bindOverlayHost.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
},
|
||||
|
||||
@@ -1486,7 +1486,7 @@ async function main() {
|
||||
const canvas = scene.canvas;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const { hitTestWorldOverlay } =
|
||||
await import('/src/overlays/worldOverlay.js');
|
||||
(await import('/src/overlays/worldOverlay.js')).worldOverlayFor(window.__godsEyeView.engine);
|
||||
const candidates = [
|
||||
[0.55, 0.72],
|
||||
[0.45, 0.72],
|
||||
|
||||
@@ -11,7 +11,7 @@ export async function checkInstallationPan(page, dir) {
|
||||
await sleep(3000);
|
||||
await page.evaluate(async () => {
|
||||
const { getOverlayPaintRect } =
|
||||
await import('/src/overlays/worldOverlay.js');
|
||||
(await import('/src/overlays/worldOverlay.js')).worldOverlayFor(window.__godsEyeView.engine);
|
||||
const g = window.__godsEyeView;
|
||||
const installations = g.dataManager.layers.get(
|
||||
'military-installations',
|
||||
@@ -188,7 +188,7 @@ export async function checkInstallationSelection(
|
||||
const { getSelectedEntityContext } =
|
||||
await import('/src/data/contextStore.js');
|
||||
const { getOverlayPaintRect, getWorldOverlayDiagnostics } =
|
||||
await import('/src/overlays/worldOverlay.js');
|
||||
(await import('/src/overlays/worldOverlay.js')).worldOverlayFor(window.__godsEyeView.engine);
|
||||
// The selected site's fill is one engine.ground polygon set
|
||||
// (render/display.js SELECTION_FILL_SET_ID), an item per footprint
|
||||
// part; the layer owns no Cesium entities or ground primitives.
|
||||
|
||||
@@ -153,7 +153,7 @@ try {
|
||||
const before = await stats();
|
||||
const overlaps = await page.evaluate(async () => {
|
||||
const { getOverlayPaintRect } =
|
||||
await import('/src/overlays/worldOverlay.js');
|
||||
(await import('/src/overlays/worldOverlay.js')).worldOverlayFor(window.__godsEyeView.engine);
|
||||
const layer = window.__godsEyeView.dataManager.layers.get(
|
||||
'military-installations',
|
||||
).module;
|
||||
@@ -214,7 +214,7 @@ try {
|
||||
);
|
||||
await sleep(300);
|
||||
result.cleanUi = await page.evaluate(async () => {
|
||||
const host = await import('/src/overlays/worldOverlay.js');
|
||||
const host = (await import('/src/overlays/worldOverlay.js')).worldOverlayFor(window.__godsEyeView.engine);
|
||||
const root = document.getElementById('world-overlay-root');
|
||||
return {
|
||||
active: document.body.classList.contains('ui-clean-view'),
|
||||
@@ -244,7 +244,7 @@ try {
|
||||
),
|
||||
);
|
||||
result.disabled = await page.evaluate(async () => {
|
||||
const host = await import('/src/overlays/worldOverlay.js');
|
||||
const host = (await import('/src/overlays/worldOverlay.js')).worldOverlayFor(window.__godsEyeView.engine);
|
||||
return host.getWorldOverlayDiagnostics().entriesBySource[
|
||||
'military-installations'
|
||||
];
|
||||
@@ -267,7 +267,7 @@ try {
|
||||
await page.evaluate(async () => {
|
||||
const C = await import('/node_modules/cesium/Build/Cesium/index.js');
|
||||
const { getOverlayPaintRect } =
|
||||
await import('/src/overlays/worldOverlay.js');
|
||||
(await import('/src/overlays/worldOverlay.js')).worldOverlayFor(window.__godsEyeView.engine);
|
||||
const g = window.__godsEyeView;
|
||||
const layer = g.dataManager.layers.get('military-installations').module;
|
||||
const overlay = document.createElement('div');
|
||||
|
||||
@@ -137,7 +137,9 @@ export async function sampleTransitPixels(page, palette) {
|
||||
.module._transitStateForTest();
|
||||
// Decode the actual billboard rasters before the paint being judged.
|
||||
const overlay = window.location
|
||||
? await import('/src/overlays/worldOverlay.js')
|
||||
? (await import('/src/overlays/worldOverlay.js')).worldOverlayFor(
|
||||
window.__godsEyeView.engine,
|
||||
)
|
||||
: null;
|
||||
const rasters = new Map();
|
||||
await Promise.all(
|
||||
|
||||
@@ -1618,7 +1618,9 @@ try {
|
||||
await wait(600);
|
||||
anchor = await page.evaluate(
|
||||
async (key, durationMs) => {
|
||||
const overlay = await import('/src/overlays/worldOverlay.js');
|
||||
const overlay = (
|
||||
await import('/src/overlays/worldOverlay.js')
|
||||
).worldOverlayFor(window.__godsEyeView.engine);
|
||||
const app = window.__godsEyeView;
|
||||
const layer = app.dataManager.layers.get('transit').module;
|
||||
const state = layer._transitStateForTest();
|
||||
|
||||
@@ -28,10 +28,10 @@ export function createHybridAnnotationRenderer(
|
||||
) {
|
||||
const world =
|
||||
worldRenderer ?? createWorldAnnotationRenderer(viewer, { engine });
|
||||
const screen = createScreenAnnotationRenderer(
|
||||
screenScene ?? viewer,
|
||||
screenOptions,
|
||||
);
|
||||
const screen = createScreenAnnotationRenderer(screenScene ?? viewer, {
|
||||
engine,
|
||||
...screenOptions,
|
||||
});
|
||||
// anno.id -> { worldProxy?, screenProxy? } — proxies own the sub-renderer state.
|
||||
const routed = new Map();
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ export function createApplicationAlpr({
|
||||
picking,
|
||||
groundFloor,
|
||||
overlays: {
|
||||
registerPaintLane: registerWorldOverlayPaintLane,
|
||||
registerPaintLane: (lane, painter, options, engine) =>
|
||||
registerWorldOverlayPaintLane(engine, lane, painter, options),
|
||||
keyholeAlpha: keyholeLabelAlphaFromGeometry,
|
||||
refreshReadout: refreshTrackedReadout,
|
||||
subscribeMapStack(callback) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { worldOverlayFor } from '../../overlays/worldOverlay.js';
|
||||
import { createDroneAirspaceLayer } from '../../layers/droneAirspace/index.js';
|
||||
import { rendererFor } from './renderers.js';
|
||||
import * as picking from '../../data/pickRegistry.js';
|
||||
@@ -12,6 +13,7 @@ import { isPointerFree } from '../../data/inputOwnership.js';
|
||||
export function createApplicationDroneAirspace(options) {
|
||||
return createDroneAirspaceLayer({
|
||||
overlayHost: {
|
||||
forEngine: worldOverlayFor,
|
||||
setEntries: overlays.setOverlayEntries,
|
||||
setVisible: overlays.setOverlaySourceVisible,
|
||||
clearSource: overlays.clearOverlaySource,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { worldOverlayFor } from '../../overlays/worldOverlay.js';
|
||||
import { rendererFor } from './renderers.js';
|
||||
import { createFirmsHeatmapLayer as createLayer } from '../../layers/firms/index.js';
|
||||
import * as render from '../../renderGovernor.js';
|
||||
@@ -30,6 +31,7 @@ export function createApplicationFirms({ surface, ...options }) {
|
||||
rendererFor('local-firms', engine, renderOptions),
|
||||
},
|
||||
overlayHost: options.overlayHost ?? {
|
||||
forEngine: worldOverlayFor,
|
||||
setEntries: overlays.setOverlayEntries,
|
||||
setVisible: overlays.setOverlaySourceVisible,
|
||||
clearSource: overlays.clearOverlaySource,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { worldOverlayFor } from '../../overlays/worldOverlay.js';
|
||||
import { rendererFor } from './renderers.js';
|
||||
import { createGhostMapsLayer } from '../../layers/ghostMaps/index.js';
|
||||
import * as picking from '../../data/pickRegistry.js';
|
||||
@@ -12,6 +13,7 @@ import { isPointerFree } from '../../data/inputOwnership.js';
|
||||
export function createApplicationGhostMaps(options) {
|
||||
return createGhostMapsLayer({
|
||||
overlayHost: {
|
||||
forEngine: worldOverlayFor,
|
||||
setEntries: overlays.setOverlayEntries,
|
||||
setVisible: overlays.setOverlaySourceVisible,
|
||||
clearSource: overlays.clearOverlaySource,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { worldOverlayFor } from '../../overlays/worldOverlay.js';
|
||||
import {
|
||||
clearOverlaySource,
|
||||
getWorldOverlayDiagnostics,
|
||||
@@ -5,6 +6,7 @@ import {
|
||||
setOverlaySourceVisible,
|
||||
} from '../../overlays/worldOverlay.js';
|
||||
export const overlayHost = Object.freeze({
|
||||
forEngine: worldOverlayFor,
|
||||
clearSource: clearOverlaySource,
|
||||
getDiagnostics: getWorldOverlayDiagnostics,
|
||||
setEntries: setOverlayEntries,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { worldOverlayFor } from '../../overlays/worldOverlay.js';
|
||||
import { rendererFor } from './renderers.js';
|
||||
import {
|
||||
createFirePerimetersLayer,
|
||||
@@ -16,6 +17,7 @@ import { isPointerFree } from '../../data/inputOwnership.js';
|
||||
export function createApplicationFirePerimeters(options) {
|
||||
return createFirePerimetersLayer({
|
||||
overlayHost: {
|
||||
forEngine: worldOverlayFor,
|
||||
setEntries: overlays.setOverlayEntries,
|
||||
setVisible: overlays.setOverlaySourceVisible,
|
||||
clearSource: overlays.clearOverlaySource,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { worldOverlayFor } from '../../overlays/worldOverlay.js';
|
||||
import { createWildlifeSightingsLayer } from '../../layers/wildlifeSightings/index.js';
|
||||
import * as picking from '../../data/pickRegistry.js';
|
||||
import * as overlays from '../../overlays/worldOverlay.js';
|
||||
@@ -10,6 +11,7 @@ import { isPointerFree } from '../../data/inputOwnership.js';
|
||||
export function createApplicationWildlifeSightings(options) {
|
||||
return createWildlifeSightingsLayer({
|
||||
overlayHost: {
|
||||
forEngine: worldOverlayFor,
|
||||
setEntries: overlays.setOverlayEntries,
|
||||
setVisible: overlays.setOverlaySourceVisible,
|
||||
clearSource: overlays.clearOverlaySource,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { worldOverlayFor } from '../overlays/worldOverlay.js';
|
||||
import { showOsmCredit, hideOsmCredit } from '../data/dataCredits.js';
|
||||
import {
|
||||
clearSelectedEntityContextForLayer,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
/** Existing standalone application operations, shared by its local layers. */
|
||||
export const localGeoJsonServices = Object.freeze({
|
||||
overlayHost: Object.freeze({
|
||||
forEngine: worldOverlayFor,
|
||||
clearSource: clearOverlaySource,
|
||||
setEntries: setOverlayEntries,
|
||||
setVisible: setOverlaySourceVisible,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { bindOverlayHost } from '../overlays/bindOverlayHost.js';
|
||||
import { worldOverlayFor } from '../overlays/worldOverlay.js';
|
||||
import { waitForSignal } from '../sources/requestDeadline.js';
|
||||
import * as Cesium from 'cesium';
|
||||
import { cesiumSdkReady, loadCesiumSdk } from '../app/loadCesium.js';
|
||||
@@ -106,6 +108,7 @@ const PASSIVE_ENABLE_ORIGINS = new Set([
|
||||
]);
|
||||
|
||||
const DEFAULT_OVERLAY_HOST = Object.freeze({
|
||||
forEngine: worldOverlayFor,
|
||||
setEntries: setOverlayEntries,
|
||||
setVisible: setOverlaySourceVisible,
|
||||
clearSource: clearOverlaySource,
|
||||
@@ -1019,6 +1022,7 @@ export function createBhoteKoshiEventLayer({
|
||||
panelStore = useBhoteKoshiPanelStore(applicationPinia()),
|
||||
} = {}) {
|
||||
let _viewer = null;
|
||||
overlayHost = bindOverlayHost(overlayHost, () => _engine);
|
||||
let _engine = null;
|
||||
let _native = null;
|
||||
let _nativeFlood = null;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { bindOverlayHost } from '../overlays/bindOverlayHost.js';
|
||||
import { worldOverlayFor } from '../overlays/worldOverlay.js';
|
||||
import * as Cesium from 'cesium';
|
||||
import { cesiumSdkReady, loadCesiumSdk } from '../app/loadCesium.js';
|
||||
import {
|
||||
@@ -151,6 +153,7 @@ const INCIDENT_ACCENT = '#ffad55';
|
||||
const INCIDENT_COLOR = () => Cesium.Color.fromCssColorString(INCIDENT_ACCENT);
|
||||
|
||||
const DEFAULT_OVERLAY_HOST = Object.freeze({
|
||||
forEngine: worldOverlayFor,
|
||||
setEntries: setOverlayEntries,
|
||||
clearSource: clearOverlaySource,
|
||||
});
|
||||
@@ -287,6 +290,7 @@ export function createBhoteKoshiLocatorLayer({
|
||||
cancelDelay = (handle) => clearTimeout(handle),
|
||||
} = {}) {
|
||||
let _viewer = null;
|
||||
overlayHost = bindOverlayHost(overlayHost, () => _engine);
|
||||
let _engine = null;
|
||||
let _native = null;
|
||||
let _pausedApproach = null;
|
||||
|
||||
+21
-11
@@ -315,21 +315,31 @@ export function initDetection(engine, layers, onModeChange) {
|
||||
);
|
||||
_hostLayoutRevision = -1;
|
||||
_calloutCount = 0;
|
||||
_hostLane = registerWorldOverlayPaintLane('detection', _paintDetectionLane, {
|
||||
id: 'detection',
|
||||
active: _mode !== MODE_OFF && !_suspended,
|
||||
target: 'detection',
|
||||
shouldPaint: _shouldPaintDetectionLane,
|
||||
});
|
||||
_hostLane = registerWorldOverlayPaintLane(
|
||||
engine,
|
||||
'detection',
|
||||
_paintDetectionLane,
|
||||
{
|
||||
id: 'detection',
|
||||
active: _mode !== MODE_OFF && !_suspended,
|
||||
target: 'detection',
|
||||
shouldPaint: _shouldPaintDetectionLane,
|
||||
},
|
||||
);
|
||||
// Registered AFTER the sensor lane so it replays the callouts that lane just
|
||||
// solved. Both sit in the 'detection' lane — the host's bottom slot — so
|
||||
// callouts still paint under every ordinary overlay card and under the
|
||||
// tracked readout, exactly where they sat before.
|
||||
_calloutLane = registerWorldOverlayPaintLane('detection', _paintCalloutLane, {
|
||||
id: 'detection-callouts',
|
||||
active: _mode !== MODE_OFF && !_suspended,
|
||||
target: 'shared',
|
||||
});
|
||||
_calloutLane = registerWorldOverlayPaintLane(
|
||||
engine,
|
||||
'detection',
|
||||
_paintCalloutLane,
|
||||
{
|
||||
id: 'detection-callouts',
|
||||
active: _mode !== MODE_OFF && !_suspended,
|
||||
target: 'shared',
|
||||
},
|
||||
);
|
||||
_hostSurface = _hostLane.surface;
|
||||
_cockpitModeListener = (event) => {
|
||||
_cockpitActive = event?.detail?.active === true;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
let overlayEngine;
|
||||
import { readShellSource } from '../testSupport/readShellSource.mjs';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
@@ -232,12 +233,6 @@ function installEnvironment({ width = 800, height = 600, dpr = 2, search = '' }
|
||||
const container = new MockElement('div');
|
||||
container.id = 'cesiumContainer';
|
||||
body.appendChild(container);
|
||||
const root = new MockElement('div');
|
||||
root.id = 'world-overlay-root';
|
||||
body.appendChild(root);
|
||||
const canvas = new MockElement('canvas');
|
||||
canvas.id = 'world-overlay-canvas';
|
||||
root.appendChild(canvas);
|
||||
|
||||
const postRender = new MockEvent();
|
||||
const viewer = {
|
||||
@@ -255,6 +250,7 @@ function installEnvironment({ width = 800, height = 600, dpr = 2, search = '' }
|
||||
|
||||
// Shell modules take the globe engine; the Cesium engine runs on the mock.
|
||||
const engine = createCesiumEngine(viewer);
|
||||
overlayEngine = engine;
|
||||
return {
|
||||
viewer,
|
||||
engine,
|
||||
@@ -267,7 +263,7 @@ function installEnvironment({ width = 800, height = 600, dpr = 2, search = '' }
|
||||
setPerformanceStep(ms) { performanceStep = ms; },
|
||||
cleanup() {
|
||||
destroyDetection();
|
||||
destroyWorldOverlay();
|
||||
destroyWorldOverlay(overlayEngine);
|
||||
engine.destroy();
|
||||
Date.now = originalDateNow;
|
||||
if (originalPath2D === undefined) delete globalThis.Path2D;
|
||||
@@ -363,7 +359,7 @@ test('detection lifecycle re-hosts unchanged painters behind the sole host liste
|
||||
const env = installEnvironment({ search: '?detectDebug=1' });
|
||||
const modes = [];
|
||||
try {
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay(overlayEngine = env.engine);
|
||||
initDetection(env.engine, [detectableLayer()], (mode) => modes.push(mode));
|
||||
assert.equal(env.postRender.listeners.size, 1);
|
||||
assert.equal(env.document.getElementById('detection-overlay'), null);
|
||||
@@ -493,7 +489,7 @@ test('detection lifecycle re-hosts unchanged painters behind the sole host liste
|
||||
setMode('DENSE');
|
||||
env.postRender.raise();
|
||||
assert.equal(env.document.getElementById('detection-overlay'), null);
|
||||
destroyWorldOverlay();
|
||||
destroyWorldOverlay(overlayEngine);
|
||||
assert.equal(env.postRender.listeners.size, 0);
|
||||
assert.equal(env.document.getElementById('world-overlay-canvas'), null);
|
||||
assert.equal(env.document.getElementById('world-overlay-detection-surface'), null);
|
||||
@@ -517,7 +513,7 @@ test('callouts stop painting the moment the last detectable object goes away', (
|
||||
type: 'AIR',
|
||||
}];
|
||||
try {
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay(overlayEngine = env.engine);
|
||||
initDetection(env.engine, [{ id: 'flights', getDetectableObjects: () => objects }], () => {});
|
||||
setMode('DENSE');
|
||||
env.advance(250);
|
||||
@@ -553,7 +549,7 @@ test('every style paints its OWN plate token through the production callout path
|
||||
// path once per style and reads the fill style the shared canvas received.
|
||||
const env = installEnvironment();
|
||||
try {
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay(overlayEngine = env.engine);
|
||||
initDetection(env.engine, [mixedTierLayer()], () => {});
|
||||
setMode('DENSE');
|
||||
settleFrame(env);
|
||||
@@ -591,7 +587,7 @@ test('space-tier callouts take the heavier plate while air contacts keep the lig
|
||||
// tiers at once can show it.
|
||||
const env = installEnvironment();
|
||||
try {
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay(overlayEngine = env.engine);
|
||||
initDetection(env.engine, [mixedTierLayer()], () => {});
|
||||
setDetectionStyle('surveillance');
|
||||
setMode('DENSE');
|
||||
@@ -619,7 +615,7 @@ test('space-tier callouts take the heavier plate while air contacts keep the lig
|
||||
test('surveillance military tier reaches the dedicated render target as shipped red', () => {
|
||||
const env = installEnvironment();
|
||||
try {
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay(overlayEngine = env.engine);
|
||||
initDetection(env.engine, [militaryLayer()], () => {});
|
||||
setDetectionStyle('surveillance');
|
||||
setMode('DENSE');
|
||||
@@ -659,7 +655,7 @@ test('Global Context echo brackets reach the canvas in the detection style colou
|
||||
pulse: { count: 1000, periodMs: 1000, startFraction: 0 },
|
||||
echo,
|
||||
});
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay(overlayEngine = env.engine);
|
||||
initDetection(env.engine, [militaryLayer()], () => {});
|
||||
setMode('DENSE');
|
||||
setDetectionStyle('normal');
|
||||
@@ -705,7 +701,7 @@ test('a Global Context echo bracket is drawn outside its contact bracket and dim
|
||||
echo,
|
||||
level: () => 0.5,
|
||||
});
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay(overlayEngine = env.engine);
|
||||
initDetection(env.engine, [militaryLayer()], () => {});
|
||||
setMode('DENSE');
|
||||
setDetectionStyle('normal');
|
||||
@@ -777,7 +773,7 @@ test('under the Cyber sonar sweep an echo is never brighter than its own bracket
|
||||
echo,
|
||||
level: () => 1,
|
||||
});
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay(overlayEngine = env.engine);
|
||||
initDetection(env.engine, [contact], () => {});
|
||||
setMode('DENSE');
|
||||
setDetectionStyle('normal');
|
||||
@@ -839,7 +835,7 @@ test('resting Global Context pulses skip echo work for the whole frame, not per
|
||||
return 0;
|
||||
},
|
||||
});
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay(overlayEngine = env.engine);
|
||||
initDetection(env.engine, [layer], () => {});
|
||||
setMode('DENSE');
|
||||
settleFrame(env);
|
||||
@@ -866,9 +862,9 @@ test('pathological detection paint holds alternate frames without freezing share
|
||||
// Opts into the telemetry banner: the held-frame assertions count banner paints.
|
||||
const env = installEnvironment({ search: '?detectDebug=1' });
|
||||
try {
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay(overlayEngine = env.engine);
|
||||
initDetection(env.engine, [detectableLayer()], () => {});
|
||||
setOverlayEntries('valve-card', [{
|
||||
setOverlayEntries(overlayEngine, 'valve-card', [{
|
||||
id: 'ambient-card',
|
||||
position: new Cesium.Cartesian3(0, 0, 0),
|
||||
variant: 'label',
|
||||
@@ -929,7 +925,7 @@ test('detection cannot resurrect a private canvas, listener, matrix, resize, cle
|
||||
assert.match(source, /const LABEL_SOLVE_INTERVAL_MS = 125;/);
|
||||
assert.match(source, /const _labelArbiter = new LabelArbiter\(\);/);
|
||||
assert.match(source, /const clipW = vp3 \* px \+ vp7 \* py \+ vp11 \* pz \+ vp15;/);
|
||||
assert.match(source, /registerWorldOverlayPaintLane\('detection', _paintDetectionLane/);
|
||||
assert.match(source, /registerWorldOverlayPaintLane\(\s*engine,\s*'detection',\s*_paintDetectionLane/);
|
||||
assert.match(source, /target: 'detection'/);
|
||||
assert.match(source, /shouldPaint: _shouldPaintDetectionLane/);
|
||||
// The three military styles used to carry three copies of these numbers.
|
||||
@@ -973,7 +969,7 @@ test('the mode banner is absent by default and present behind the flag', () => {
|
||||
const painted = (search) => {
|
||||
const env = installEnvironment({ search });
|
||||
try {
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay(overlayEngine = env.engine);
|
||||
initDetection(env.engine, [detectableLayer()], () => {});
|
||||
setMode('DENSE');
|
||||
env.advance(250);
|
||||
@@ -986,7 +982,7 @@ test('the mode banner is absent by default and present behind the flag', () => {
|
||||
return bannerPaints(env);
|
||||
} finally {
|
||||
destroyDetection();
|
||||
destroyWorldOverlay();
|
||||
destroyWorldOverlay(overlayEngine);
|
||||
env.cleanup();
|
||||
}
|
||||
};
|
||||
@@ -1000,7 +996,7 @@ test('detection telemetry stays reachable programmatically with the banner hidde
|
||||
// is the supported way to read them, and is what the QA harnesses use.
|
||||
const env = installEnvironment();
|
||||
try {
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay(overlayEngine = env.engine);
|
||||
initDetection(env.engine, [detectableLayer()], () => {});
|
||||
setMode('DENSE');
|
||||
env.advance(250);
|
||||
@@ -1012,7 +1008,7 @@ test('detection telemetry stays reachable programmatically with the banner hidde
|
||||
assert.equal(typeof diagnostics.densityPct, 'number');
|
||||
} finally {
|
||||
destroyDetection();
|
||||
destroyWorldOverlay();
|
||||
destroyWorldOverlay(overlayEngine);
|
||||
env.cleanup();
|
||||
}
|
||||
});
|
||||
@@ -1028,7 +1024,7 @@ test('civilian and military AIR brackets cover front, left, and right at Sparse
|
||||
metric: 'FL120',
|
||||
type: 'AIR',
|
||||
}));
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay(overlayEngine = env.engine);
|
||||
initDetection(env.engine, [{ id: layerId, getDetectableObjects: () => objects }], () => {});
|
||||
setDetectionTuning({ densityPct: 25 });
|
||||
setMode('SPARSE');
|
||||
@@ -1046,7 +1042,7 @@ test('civilian and military AIR brackets cover front, left, and right at Sparse
|
||||
'Sparse keeps its documented budget instead of silently becoming Dense');
|
||||
} finally {
|
||||
destroyDetection();
|
||||
destroyWorldOverlay();
|
||||
destroyWorldOverlay(overlayEngine);
|
||||
env.cleanup();
|
||||
}
|
||||
}
|
||||
@@ -1086,7 +1082,7 @@ test('the backdrop feather reaches the canvas as a lighter plate against sky', (
|
||||
// the shared canvas actually received for each plate.
|
||||
const env = installEnvironment();
|
||||
try {
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay(overlayEngine = env.engine);
|
||||
initDetection(env.engine, [backdropLayer()], () => {});
|
||||
setDetectionStyle('normal');
|
||||
setMode('DENSE');
|
||||
@@ -1144,7 +1140,7 @@ test('a transit vehicle is a contact: box, route, mode and operator on the share
|
||||
},
|
||||
];
|
||||
try {
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay(overlayEngine = env.engine);
|
||||
initDetection(
|
||||
env.engine,
|
||||
[{ id: 'transit', getDetectableObjects: () => objects }],
|
||||
|
||||
@@ -198,6 +198,7 @@ test('consumer build includes only infrastructure code and engine-free geometry,
|
||||
'src/layers/infrastructure/index.js',
|
||||
'src/layers/infrastructure/render/display.js',
|
||||
'src/layers/shared/color.js',
|
||||
'src/overlays/bindOverlayHost.js',
|
||||
'src/sources/infrastructureData.js',
|
||||
'src/sources/requestDeadline.js',
|
||||
]);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createWorldOverlay } from '../overlays/worldOverlay.js';
|
||||
import { createRenderGovernor } from '../renderGovernor.js';
|
||||
import { createPickRegistry } from './pickRegistry.js';
|
||||
import { createSelectionDispatcher } from './selectionDispatcher.js';
|
||||
@@ -9,6 +10,7 @@ const scenes = new WeakMap();
|
||||
/** Create the services for one attachment, including reuse of a parked engine. */
|
||||
export function createSceneServices(engine, options = {}) {
|
||||
scenes.get(engine)?.destroy();
|
||||
const overlay = createWorldOverlay(engine);
|
||||
const render = createRenderGovernor(engine);
|
||||
const picking = createPickRegistry();
|
||||
const selection = createSelectionDispatcher(engine, { ...options, picking });
|
||||
@@ -17,11 +19,13 @@ export function createSceneServices(engine, options = {}) {
|
||||
picking,
|
||||
selection,
|
||||
render,
|
||||
overlay,
|
||||
destroy() {
|
||||
if (destroyed) return;
|
||||
destroyed = true;
|
||||
selection.destroy();
|
||||
picking.destroy();
|
||||
overlay.destroy();
|
||||
render.destroy();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { createDroneAirspaceLayer } from '../layers/droneAirspace/index.js';
|
||||
import { overlayHost } from '../app/layers/overlayHost.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { GROUND_HARNESSES } from '../testSupport/groundDisplayEngines.mjs';
|
||||
@@ -135,3 +137,60 @@ for (const makeHarness of GROUND_HARNESSES) {
|
||||
assert.equal(b.picking.findPickOwner('shared-id'), 'flights');
|
||||
});
|
||||
}
|
||||
|
||||
for (const makeHarness of GROUND_HARNESSES) {
|
||||
test(`${makeHarness.name}: a retained selected airspace card moves between overlay services`, async (t) => {
|
||||
const first = makeHarness();
|
||||
const second = makeHarness();
|
||||
const a = createSceneServices(first.engine);
|
||||
const b = createSceneServices(second.engine);
|
||||
let requests = 0;
|
||||
const ring = [
|
||||
[-77.04, 38.89],
|
||||
[-77.03, 38.89],
|
||||
[-77.03, 38.9],
|
||||
[-77.04, 38.89],
|
||||
];
|
||||
const layer = createDroneAirspaceLayer({
|
||||
source: {
|
||||
async getSnapshot() {
|
||||
requests++;
|
||||
return [
|
||||
{
|
||||
stableId: '310',
|
||||
name: 'P-56',
|
||||
typeCode: 'P',
|
||||
type: 'Prohibited',
|
||||
polygons: [[ring]],
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
overlayHost,
|
||||
picking,
|
||||
pointer: { isPointerFree: () => true },
|
||||
});
|
||||
t.after(() => {
|
||||
layer.destroy();
|
||||
a.destroy();
|
||||
b.destroy();
|
||||
first.destroy();
|
||||
second.destroy();
|
||||
});
|
||||
layer.init(null, { engine: first.engine });
|
||||
layer.enable();
|
||||
await layer.update();
|
||||
first.click({ id: 'drone-airspace:310:0' });
|
||||
assert.equal(a.overlay.getDiagnostics().entryCount, 1);
|
||||
assert.equal(b.overlay.getDiagnostics().entryCount, 0);
|
||||
layer.detachRenderer();
|
||||
assert.equal(a.overlay.getDiagnostics().entryCount, 0);
|
||||
layer.attachRenderer(null, { engine: second.engine });
|
||||
assert.equal(requests, 1);
|
||||
assert.equal(b.overlay.getDiagnostics().entryCount, 1);
|
||||
a.destroy();
|
||||
assert.equal(b.overlay.getDiagnostics().entryCount, 1);
|
||||
layer.disable();
|
||||
assert.equal(b.overlay.getDiagnostics().entryCount, 0);
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createApplicationCables } from '../app/layers/submarineCables.js';
|
||||
import { overlayHost } from '../app/layers/overlayHost.js';
|
||||
import { worldOverlayFor } from '../overlays/worldOverlay.js';
|
||||
import { createCableOverlayPublisher as createPublisher } from '../layers/submarineCables/index.js';
|
||||
import { createBundledCableSource } from '../layers/submarineCables/bundledSource.js';
|
||||
export function createTeleGeographySubmarineCableLayer(options = {}) {
|
||||
@@ -9,7 +9,7 @@ export function createTeleGeographySubmarineCableLayer(options = {}) {
|
||||
});
|
||||
}
|
||||
export function createCableOverlayPublisher(options = {}) {
|
||||
return createPublisher({ host: overlayHost, ...options });
|
||||
return createPublisher({ host: worldOverlayFor(options.engine), ...options });
|
||||
}
|
||||
export {
|
||||
CABLE_REFERENCE_LABEL_WINNER_CAP,
|
||||
|
||||
@@ -28,9 +28,9 @@ const TRACKED_BILLBOARD_SCALE = Object.freeze({
|
||||
});
|
||||
|
||||
const DEFAULT_TRACKED_OVERLAY_HOST = Object.freeze({
|
||||
setEntries: setOverlayEntries,
|
||||
setVisible: setOverlaySourceVisible,
|
||||
clearSource: clearOverlaySource,
|
||||
setEntries: (...args) => setOverlayEntries(_engine, ...args),
|
||||
setVisible: (...args) => setOverlaySourceVisible(_engine, ...args),
|
||||
clearSource: (...args) => clearOverlaySource(_engine, ...args),
|
||||
});
|
||||
|
||||
// The globe engine: the followed target is engine.camera.following().
|
||||
|
||||
@@ -71,7 +71,8 @@ function markScale(h) {
|
||||
export function createScreenAnnotationRenderer(
|
||||
target,
|
||||
{
|
||||
overlayPaintRect = getOverlayPaintRect,
|
||||
engine,
|
||||
overlayPaintRect = (...args) => getOverlayPaintRect(engine, ...args),
|
||||
activeTrackedReadoutId = getActiveTrackedReadoutId,
|
||||
store = useScreenAnnotationsStore(applicationPinia()),
|
||||
} = {},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
import { fromDegrees } from '../../geo/cartesian3.js';
|
||||
import { toDegrees } from '../../geo/math.js';
|
||||
import { createAirQualityDisplay } from './render/display.js';
|
||||
@@ -33,6 +34,8 @@ export function createAirQualityLayer({
|
||||
if (typeof source?.getSnapshot !== 'function')
|
||||
throw new TypeError('Air quality requires a snapshot source');
|
||||
if (!overlayHost) throw new TypeError('Air quality requires an overlay host');
|
||||
let sceneEngine = null;
|
||||
overlayHost = bindOverlayHost(overlayHost, () => sceneEngine);
|
||||
let initialized = false;
|
||||
let _request = null;
|
||||
let renderer = null;
|
||||
@@ -79,6 +82,7 @@ export function createAirQualityLayer({
|
||||
attachRenderer(viewer, { engine = null } = {}) {
|
||||
if (!initialized || renderer)
|
||||
throw new Error('Invalid air quality scene attachment');
|
||||
sceneEngine = engine;
|
||||
renderer = createRenderer({ engine });
|
||||
renderer.setVisible(_enabled);
|
||||
overlayHost.setVisible(AIR_QUALITY_OVERLAY_SOURCE_ID, _enabled);
|
||||
@@ -90,6 +94,7 @@ export function createAirQualityLayer({
|
||||
overlayHost.setVisible(AIR_QUALITY_OVERLAY_SOURCE_ID, false);
|
||||
renderer?.destroy();
|
||||
renderer = null;
|
||||
sceneEngine = null;
|
||||
},
|
||||
|
||||
enable() {
|
||||
|
||||
@@ -222,7 +222,14 @@ export function createAlprDisplay(engine, { state, services, source }) {
|
||||
|
||||
const overlay = createAlprOverlayCore({
|
||||
state,
|
||||
services,
|
||||
services: {
|
||||
...services,
|
||||
overlays: {
|
||||
...services.overlays,
|
||||
registerPaintLane: (...args) =>
|
||||
services.overlays.registerPaintLane?.(...args, engine),
|
||||
},
|
||||
},
|
||||
onSurfaceChange: refreshPlacements,
|
||||
engine: {
|
||||
marker: (id) => markers.get(id),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
export function createState({ services }) {
|
||||
const { setOverlayEntries, setOverlaySourceVisible, clearOverlaySource } =
|
||||
services.overlays;
|
||||
bindOverlayHost(services.overlays, () => layerState._engine);
|
||||
const layerState = {};
|
||||
|
||||
layerState.DEFAULT_OVERLAY_HOST = Object.freeze({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
import { createCalibration } from './calibration.js';
|
||||
import { createTesting } from './testing.js';
|
||||
import { createProjection } from './projection.js';
|
||||
@@ -27,8 +28,12 @@ export function createCctvLayer({ services, source, rendererFactory }) {
|
||||
)
|
||||
)
|
||||
throw new TypeError('A camera source is required');
|
||||
const state = createState({ services });
|
||||
const parts = {};
|
||||
services = {
|
||||
...services,
|
||||
overlays: bindOverlayHost(services.overlays, () => parts.renderer?.engine),
|
||||
};
|
||||
const state = createState({ services });
|
||||
const context = { state, services, parts, source, rendererFactory };
|
||||
parts.calibration = createCalibration(context);
|
||||
parts.testing = createTesting(context);
|
||||
|
||||
@@ -65,7 +65,7 @@ const number = (value, unit) =>
|
||||
*/
|
||||
export function createCyclonesLayer({
|
||||
feed,
|
||||
hitTestOverlay = hitTestWorldOverlay,
|
||||
hitTestOverlay = null,
|
||||
overlayHost,
|
||||
context = null,
|
||||
createRendering = null,
|
||||
@@ -75,6 +75,7 @@ export function createCyclonesLayer({
|
||||
if (typeof feed?.getSnapshot !== 'function')
|
||||
throw new TypeError('Cyclones require a snapshot source');
|
||||
let engine = null;
|
||||
hitTestOverlay ??= (...args) => hitTestWorldOverlay(engine, ...args);
|
||||
let rendering = null,
|
||||
snapshot = null,
|
||||
request = null,
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
clearOverlaySource,
|
||||
setOverlayEntries,
|
||||
setOverlaySourceVisible,
|
||||
} from '../../overlays/worldOverlay.js';
|
||||
import { worldOverlayFor } from '../../overlays/worldOverlay.js';
|
||||
|
||||
/**
|
||||
* @module cyclones/labels
|
||||
@@ -31,12 +27,6 @@ export const CYCLONE_MARKER_PX = Object.freeze({
|
||||
const STORM_PRIORITY = 1000;
|
||||
const LEAD_PRIORITY = 500;
|
||||
|
||||
const CYCLONE_OVERLAY_HOST = Object.freeze({
|
||||
setEntries: setOverlayEntries,
|
||||
setVisible: setOverlaySourceVisible,
|
||||
clearSource: clearOverlaySource,
|
||||
});
|
||||
|
||||
/** Fire-card anchor clearance: the gap and leader start follow the marker. */
|
||||
function markerClearance(markerPx) {
|
||||
const gapPx = Math.max(12, markerPx + 8);
|
||||
@@ -139,7 +129,10 @@ export function cycloneStormIdFromEntryId(entryId) {
|
||||
* Publish the renderer's committed anchors and selection to the overlay host.
|
||||
* @param {{host?:{setEntries:Function,setVisible:Function,clearSource:Function}}} [options]
|
||||
*/
|
||||
export function createCycloneLabels({ host = CYCLONE_OVERLAY_HOST } = {}) {
|
||||
export function createCycloneLabels({
|
||||
engine,
|
||||
host = worldOverlayFor(engine),
|
||||
} = {}) {
|
||||
let storms = [];
|
||||
let selectedId = null;
|
||||
let published = false;
|
||||
|
||||
@@ -81,9 +81,7 @@ export function cycloneSphereVisible(sphere, camera) {
|
||||
export function createCyclonesDisplay(engine, { overlayHost } = {}) {
|
||||
if (!engine?.movers || !engine?.ground)
|
||||
throw new TypeError('The cyclone display needs a globe engine');
|
||||
const labels = createCycloneLabels(
|
||||
overlayHost ? { host: overlayHost } : undefined,
|
||||
);
|
||||
const labels = createCycloneLabels({ engine, host: overlayHost });
|
||||
// Mover set ids stay unique when a set is rebuilt larger.
|
||||
let setSeq = 0;
|
||||
const pointSet = (role, capacity, outlinePx, sizePx, order) =>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
/**
|
||||
* @module layers/directions
|
||||
* @description Keyless A→B directions on the globe: drive, walk or cycle.
|
||||
@@ -871,6 +872,10 @@ export function createDirectionsLayer({
|
||||
* tests swap it. Resolved on use, not at import, because the services arrive
|
||||
* after this module is evaluated.
|
||||
*/
|
||||
services = {
|
||||
...services,
|
||||
overlays: bindOverlayHost(services.overlays, () => _engine),
|
||||
};
|
||||
const DEFAULT_OVERLAY_HOST = Object.freeze({
|
||||
setEntries: (...args) => services.overlays.setOverlayEntries(...args),
|
||||
setVisible: (...args) => services.overlays.setOverlaySourceVisible(...args),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
import * as geo from '../../geo/index.js';
|
||||
import { msg, t } from '../../i18n/index.js';
|
||||
import { createDroneAirspaceDisplay } from './render/display.js';
|
||||
@@ -60,6 +61,7 @@ export function createDroneAirspaceLayer({
|
||||
/** @type {Map<string, {stableId: string, anchor: {lon: number, lat: number}}>} */
|
||||
const _rowById = new Map();
|
||||
|
||||
overlayHost = bindOverlayHost(overlayHost, () => sceneEngine);
|
||||
const canSelect = () => overlayHost && picking;
|
||||
|
||||
function publishSelectedCard() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
// Source errors stay English until the layer panel presents them.
|
||||
const msg = (text) => text;
|
||||
import * as geo from '../../geo/index.js';
|
||||
@@ -31,6 +32,8 @@ export function createEarthquakesLayer({
|
||||
if (!overlayHost) throw new TypeError('Earthquakes require an overlay host');
|
||||
if (typeof renderer?.create !== 'function')
|
||||
throw new TypeError('Earthquakes require a renderer factory');
|
||||
let sceneEngine = null;
|
||||
overlayHost = bindOverlayHost(overlayHost, () => sceneEngine);
|
||||
let initialized = false;
|
||||
let observations = [];
|
||||
let discs = [];
|
||||
@@ -73,6 +76,7 @@ export function createEarthquakesLayer({
|
||||
attachRenderer(viewer, { engine = null } = {}) {
|
||||
if (!initialized || _renderer)
|
||||
throw new Error('Invalid earthquake scene attachment');
|
||||
sceneEngine = engine;
|
||||
_renderer = renderer.create({ engine });
|
||||
if (!_renderer) throw new Error('Earthquake renderer unavailable');
|
||||
_renderer.setVisible(_enabled);
|
||||
@@ -86,6 +90,7 @@ export function createEarthquakesLayer({
|
||||
overlayHost.setVisible(EARTHQUAKE_OVERLAY_SOURCE_ID, false);
|
||||
_renderer?.destroy();
|
||||
_renderer = null;
|
||||
sceneEngine = null;
|
||||
},
|
||||
|
||||
enable(viewer) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
import { createModel } from './model.js';
|
||||
import { createIngestion } from './ingestion.js';
|
||||
import { createRendering } from './rendering.js';
|
||||
@@ -20,6 +21,13 @@ export function createFirmsHeatmapLayer({ services, feed, ...config }) {
|
||||
if (typeof config.renderer?.create !== 'function')
|
||||
throw new TypeError('Fires require a renderer factory');
|
||||
const layerState = createFirmsState({ services, config });
|
||||
config = {
|
||||
...config,
|
||||
overlayHost: bindOverlayHost(
|
||||
config.overlayHost,
|
||||
() => layerState._renderer?.engine,
|
||||
),
|
||||
};
|
||||
const components = {};
|
||||
const context = { layerState, services, config, components, feed };
|
||||
components.model = createModel(context);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
import { fromDegrees } from '../../geo/cartesian3.js';
|
||||
import {
|
||||
decodeXml,
|
||||
@@ -186,6 +187,7 @@ export function createGhostMapsLayer({
|
||||
|
||||
const notifyRow = () => _rowListener?.();
|
||||
const render = () => _display?.requestRender();
|
||||
overlayHost = bindOverlayHost(overlayHost, () => sceneEngine);
|
||||
const canSelect = () => overlayHost && picking && createRenderer;
|
||||
|
||||
function wanted(product) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../../overlays/bindOverlayHost.js';
|
||||
// The datacenters and dams on the globe contract (engine.movers,
|
||||
// engine.lines, engine.ground, engine.surface), the same on every engine.
|
||||
// Each point or polygon feature has a reference:
|
||||
@@ -113,7 +114,7 @@ export function createInfrastructureDisplay(
|
||||
const footprintFill = [css.red, css.green, css.blue, FOOTPRINT_ALPHA];
|
||||
const publisher = createLocalInfrastructureOverlayPublisher({
|
||||
sourceId: id,
|
||||
host: services.overlayHost,
|
||||
host: bindOverlayHost(services.overlayHost, () => engine),
|
||||
});
|
||||
const key = (featureId) => `${id}:${featureId}`;
|
||||
const wake = (reason = id) => engine.render.request(reason);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../../overlays/bindOverlayHost.js';
|
||||
// Mapped Installations on the globe contract (engine.movers, engine.ground,
|
||||
// engine.input, engine.camera), the same on every engine:
|
||||
//
|
||||
@@ -164,7 +165,7 @@ export function createInstallationsDisplay(
|
||||
state,
|
||||
services,
|
||||
parts,
|
||||
overlayHost,
|
||||
overlayHost: bindOverlayHost(overlayHost, () => engine),
|
||||
});
|
||||
|
||||
let initialized = false;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
import * as geo from '../../geo/index.js';
|
||||
|
||||
export function createState({ services }) {
|
||||
const { setOverlayEntries, setOverlaySourceVisible, clearOverlaySource } =
|
||||
services.overlays;
|
||||
bindOverlayHost(services.overlays, () => state._engine);
|
||||
const state = {};
|
||||
|
||||
state.DEFAULT_OVERLAY_HOST = Object.freeze({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
import * as geo from '../../geo/index.js';
|
||||
import { msg } from '../../i18n/marker.js';
|
||||
import { OSM_PLACES_LAYER_ID, OSM_PLACE_COLOR } from './model.js';
|
||||
@@ -26,6 +27,8 @@ export function createOsmPlacesLayer({
|
||||
createRenderer = ({ engine }) => createOsmPlacesDisplay(engine),
|
||||
} = {}) {
|
||||
if (!overlayHost) throw new TypeError('OSM Places requires an overlay host');
|
||||
let sceneEngine = null;
|
||||
overlayHost = bindOverlayHost(overlayHost, () => sceneEngine);
|
||||
let initialized = false;
|
||||
let renderer = null;
|
||||
let enabled = false;
|
||||
@@ -101,6 +104,7 @@ export function createOsmPlacesLayer({
|
||||
attachRenderer(viewer, { engine = null } = {}) {
|
||||
if (!initialized || renderer)
|
||||
throw new Error('Invalid OSM Places scene attachment');
|
||||
sceneEngine = engine;
|
||||
renderer = createRenderer({ viewer, engine });
|
||||
renderer.setVisible(enabled);
|
||||
if (enabled) overlayHost.setVisible(OVERLAY_SOURCE_ID, true);
|
||||
@@ -112,6 +116,7 @@ export function createOsmPlacesLayer({
|
||||
overlayHost.setVisible(OVERLAY_SOURCE_ID, false);
|
||||
renderer?.destroy();
|
||||
renderer = null;
|
||||
sceneEngine = null;
|
||||
},
|
||||
|
||||
enable() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
import { fromDegrees } from '../../geo/cartesian3.js';
|
||||
import { createPerimetersDisplay } from './render/display.js';
|
||||
import {
|
||||
@@ -89,6 +90,7 @@ export function createFirePerimetersLayer({
|
||||
_linkRequest = null;
|
||||
}
|
||||
|
||||
overlayHost = bindOverlayHost(overlayHost, () => sceneEngine);
|
||||
const canSelect = () => overlayHost && picking;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
import { createCatalogModel } from './catalogModel.js';
|
||||
import { createLabels } from './labels.js';
|
||||
import { createClustering } from './clustering.js';
|
||||
@@ -20,8 +21,12 @@ import { createState } from './state.js';
|
||||
|
||||
/** Construct one layer with its own scene state and supplied application services. */
|
||||
export function createRadioLayer({ services, source, rendererFactory }) {
|
||||
const state = createState({ services });
|
||||
const parts = {};
|
||||
services = {
|
||||
...services,
|
||||
overlays: bindOverlayHost(services.overlays, () => parts.renderer?.engine),
|
||||
};
|
||||
const state = createState({ services });
|
||||
const context = { state, services, parts, source, rendererFactory };
|
||||
parts.catalogModel = createCatalogModel(context);
|
||||
parts.labels = createLabels(context);
|
||||
|
||||
@@ -50,12 +50,12 @@ export function createLifecycle({
|
||||
parts.interaction.removeInteraction();
|
||||
clearTimeout(layerState._overlayPublishTimer);
|
||||
layerState._overlayPublishTimer = null;
|
||||
clearOverlaySource(RADIO_OVERLAY_SOURCE_ID);
|
||||
parts.renderer?.destroy();
|
||||
parts.renderer = null;
|
||||
layerState._renderById.clear();
|
||||
layerState._selectedPosition = null;
|
||||
layerState._lastHorizonCameraPosition = null;
|
||||
clearOverlaySource(RADIO_OVERLAY_SOURCE_ID);
|
||||
},
|
||||
|
||||
/** Show stations. Enabling or preset restoration never starts audio. */
|
||||
@@ -109,9 +109,9 @@ export function createLifecycle({
|
||||
if (layerState._audio) layerState._audio.volume = DEFAULT_RADIO_VOLUME;
|
||||
layerState._audio = null;
|
||||
layerState._userVolume = DEFAULT_RADIO_VOLUME;
|
||||
clearOverlaySource(RADIO_OVERLAY_SOURCE_ID);
|
||||
parts.renderer?.destroy();
|
||||
parts.renderer = null;
|
||||
clearOverlaySource(RADIO_OVERLAY_SOURCE_ID);
|
||||
layerState._stations = [];
|
||||
layerState._acceptedCatalogSnapshot = EMPTY_ACCEPTED_CATALOG_SNAPSHOT;
|
||||
layerState._stationById.clear();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
import * as geo from '../../geo/index.js';
|
||||
|
||||
export function createState({ services }) {
|
||||
const { setOverlayEntries, setOverlaySourceVisible, clearOverlaySource } =
|
||||
services.overlays;
|
||||
bindOverlayHost(services.overlays, () => state._engine);
|
||||
const state = {};
|
||||
|
||||
state.DEFAULT_OVERLAY_HOST = Object.freeze({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../../overlays/bindOverlayHost.js';
|
||||
// The submarine cables on the globe contract, the same on every engine:
|
||||
// - cable paths: one engine.ground corridor set (2.5 px, each cable's own
|
||||
// colour at 0.92), draped on the active map stack's surface;
|
||||
@@ -134,7 +135,9 @@ export function createCablesDisplay(
|
||||
) {
|
||||
if (!engine?.ground || !engine?.movers || !engine?.lines)
|
||||
throw new TypeError('The cables display needs a globe engine');
|
||||
const publisher = createCableOverlayPublisher({ host: overlayHost });
|
||||
const publisher = createCableOverlayPublisher({
|
||||
host: bindOverlayHost(overlayHost, () => engine),
|
||||
});
|
||||
const gate = createCableReferenceSweepGate({ now: sweepClock });
|
||||
// Reference records by id (their pickId), in creation order.
|
||||
const records = new Map();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
import { createTransitSource } from './source.js';
|
||||
import { createTrails } from './trails.js';
|
||||
import { createState } from './state.js';
|
||||
@@ -39,8 +40,12 @@ export function createTransitLayer({
|
||||
'A transit source needs snapshot and history operations',
|
||||
);
|
||||
}
|
||||
const state = createState({ services });
|
||||
const parts = {};
|
||||
services = {
|
||||
...services,
|
||||
overlays: bindOverlayHost(services.overlays, () => parts.renderer?.engine),
|
||||
};
|
||||
const state = createState({ services });
|
||||
const context = { state, services, parts, source, rendererFactory };
|
||||
parts.trails = createTrails(context);
|
||||
parts.height = createHeight(context);
|
||||
|
||||
@@ -339,10 +339,10 @@ export function createLifecycle({
|
||||
*/
|
||||
destroy(viewer) {
|
||||
this.disable(viewer);
|
||||
state._overlayHost.clearSource(TRANSIT_SELECTED_OVERLAY_SOURCE_ID);
|
||||
parts.renderer?.destroy();
|
||||
parts.renderer = null;
|
||||
state._markers = state._animatedMarkers = null;
|
||||
state._overlayHost.clearSource(TRANSIT_SELECTED_OVERLAY_SOURCE_ID);
|
||||
parts.height.clear();
|
||||
unbindStyleEvents();
|
||||
state._dataManager = null;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
import { createVesselFeed } from './ingestion.js';
|
||||
import { VesselRecords } from './records.js';
|
||||
import * as geo from '../../geo/index.js';
|
||||
@@ -9,7 +10,10 @@ export function createVesselState({ source, services }) {
|
||||
setOverlaySourceVisible,
|
||||
clearOverlaySource,
|
||||
hitTestWorldOverlay,
|
||||
} = services.overlay;
|
||||
} = bindOverlayHost(
|
||||
services.overlay,
|
||||
() => vesselState.state.renderer?.engine,
|
||||
);
|
||||
const vesselState = {};
|
||||
|
||||
vesselState._source = source;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bindOverlayHost } from '../../overlays/bindOverlayHost.js';
|
||||
import { createPhotoSlots } from './photos.js';
|
||||
import { createRendering } from './rendering.js';
|
||||
import { createSelection } from './selection.js';
|
||||
@@ -73,6 +74,7 @@ export function createWildlifeSightingsLayer({
|
||||
retryAt: 0,
|
||||
failures: 0,
|
||||
};
|
||||
overlayHost = bindOverlayHost(overlayHost, () => state.engine);
|
||||
const parts = {};
|
||||
const services = {
|
||||
source,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/** Resolve a retained model's overlay calls against its current attachment. */
|
||||
export function bindOverlayHost(host, getEngine) {
|
||||
if (!host?.forEngine) return host;
|
||||
return Object.fromEntries(
|
||||
Object.entries(host)
|
||||
.filter(([, value]) => typeof value === 'function')
|
||||
.map(([name]) => [
|
||||
name,
|
||||
(...args) => host.forEngine(getEngine())?.[name]?.(...args),
|
||||
]),
|
||||
);
|
||||
}
|
||||
+2360
-2241
File diff suppressed because it is too large.
Load diff
+390
-276
File diff suppressed because it is too large.
Load diff
@@ -1,3 +1,4 @@
|
||||
let overlayEngine;
|
||||
import * as Cesium from 'cesium';
|
||||
import { createCesiumEngine } from '../globe/cesium/createCesiumEngine.js';
|
||||
import * as geo from '../geo/index.js';
|
||||
@@ -1321,7 +1322,7 @@ function main() {
|
||||
// The Navara projector profile swaps the engine's projector before the
|
||||
// host reads it.
|
||||
if (PROFILE === 'navara-projector') env.engine.projector = navaraProjector();
|
||||
initWorldOverlay(env.engine);
|
||||
initWorldOverlay((overlayEngine = env.engine));
|
||||
const workload =
|
||||
PROFILE === 'phase6-detection'
|
||||
? buildPhase6DetectionWorkload(ENTRY_COUNT)
|
||||
@@ -1376,16 +1377,21 @@ function main() {
|
||||
setDetectionMode('DENSE');
|
||||
} else if (workload.registrations) {
|
||||
for (const registration of workload.registrations) {
|
||||
setOverlayEntries(registration.sourceId, registration.entries, {
|
||||
...registration.options,
|
||||
// Exercise the stricter moving-source solve cadence while retaining
|
||||
// the production source ids, entries, caps, and bounded cohorts.
|
||||
moving: true,
|
||||
solveIntervalMs,
|
||||
});
|
||||
setOverlayEntries(
|
||||
overlayEngine,
|
||||
registration.sourceId,
|
||||
registration.entries,
|
||||
{
|
||||
...registration.options,
|
||||
// Exercise the stricter moving-source solve cadence while retaining
|
||||
// the production source ids, entries, caps, and bounded cohorts.
|
||||
moving: true,
|
||||
solveIntervalMs,
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setOverlayEntries('alloc-probe', entries, {
|
||||
setOverlayEntries(overlayEngine, 'alloc-probe', entries, {
|
||||
collisionCapacity: 96,
|
||||
cohortLimit: 256,
|
||||
moving: true,
|
||||
@@ -1411,7 +1417,7 @@ function main() {
|
||||
globalThis.gc();
|
||||
for (let i = 0; i < CHUNK_FRAMES; i++) tick();
|
||||
}
|
||||
const warm = getWorldOverlayDiagnostics();
|
||||
const warm = getWorldOverlayDiagnostics(overlayEngine);
|
||||
const detectionWarm = detectionActive ? getDetectionDiagnostics() : null;
|
||||
const painted = detectionActive
|
||||
? detectionWarm.visibleCount
|
||||
@@ -1434,7 +1440,7 @@ function main() {
|
||||
}
|
||||
const solveRevisionAfter = detectionActive
|
||||
? getDetectionDiagnostics().solveRevision
|
||||
: getWorldOverlayDiagnostics().solveRevision;
|
||||
: getWorldOverlayDiagnostics(overlayEngine).solveRevision;
|
||||
const sorted = chunkRates.slice().sort((a, b) => a - b);
|
||||
const maxBytesPerFrame = sorted[sorted.length - 1];
|
||||
const medianBytesPerFrame = sorted[Math.floor(sorted.length / 2)];
|
||||
@@ -1470,7 +1476,7 @@ function main() {
|
||||
);
|
||||
|
||||
if (detectionActive) destroyDetection();
|
||||
destroyWorldOverlay();
|
||||
destroyWorldOverlay(overlayEngine);
|
||||
env.engine.destroy();
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,19 @@ export function installWorldOverlayFontInvalidation() {
|
||||
);
|
||||
}
|
||||
|
||||
let _drawHosts = 0;
|
||||
|
||||
/** Keep shared font measurements live while any scene host is attached. */
|
||||
export function retainWorldOverlayDraw() {
|
||||
_drawHosts++;
|
||||
let released = false;
|
||||
return () => {
|
||||
if (released) return;
|
||||
released = true;
|
||||
if (--_drawHosts === 0) destroyWorldOverlayDraw();
|
||||
};
|
||||
}
|
||||
|
||||
/** Remove font hooks and cached measurements when the overlay host is destroyed. */
|
||||
export function destroyWorldOverlayDraw() {
|
||||
_fontInvalidationGeneration++;
|
||||
|
||||
@@ -109,8 +109,6 @@ export class StyleManager extends ShellScene {
|
||||
CelestialRing,
|
||||
initTrackedReadout,
|
||||
initWorldOverlay,
|
||||
subscribeWorldOverlayActions,
|
||||
activateWorldOverlayAction,
|
||||
initDetection,
|
||||
setDetectionStyle,
|
||||
trafficLayer,
|
||||
@@ -313,14 +311,6 @@ export class StyleManager extends ShellScene {
|
||||
});
|
||||
|
||||
this._initSceneOverlays();
|
||||
// Its focusable mirror of the painted cards, which the scene chrome
|
||||
// renders.
|
||||
const overlayActionsStore = useWorldOverlayActionsStore(applicationPinia());
|
||||
overlayActionsStore.connect({ activate: activateWorldOverlayAction });
|
||||
this._overlayActionsUnsubscribe = subscribeWorldOverlayActions((view) =>
|
||||
overlayActionsStore.publish(view),
|
||||
);
|
||||
|
||||
this._displayBindings = new DisplayBindings({
|
||||
viewer,
|
||||
services: {
|
||||
|
||||
+14
-1
@@ -1,3 +1,4 @@
|
||||
import { useWorldOverlayActionsStore } from './stores/worldOverlayActions.js';
|
||||
import { renderGovernorFor } from '../renderGovernor.js';
|
||||
import { retainsLayerSession } from '../app/layerSession.js';
|
||||
import { resolveImageryHost } from '../layers/weather/imageryHost.js';
|
||||
@@ -253,6 +254,16 @@ export class ShellScene extends ShellFacade {
|
||||
// detection and tracked-readout initialize. It stays transparent until a
|
||||
// production source explicitly registers entries.
|
||||
initWorldOverlay(engine);
|
||||
const overlayActionsStore = useWorldOverlayActionsStore(applicationPinia());
|
||||
this._overlayActionsUnsubscribe?.();
|
||||
overlayActionsStore.connect({
|
||||
activate: (...args) =>
|
||||
this.services.activateWorldOverlayAction(engine, ...args),
|
||||
});
|
||||
this._overlayActionsUnsubscribe =
|
||||
this.services.subscribeWorldOverlayActions?.(engine, (view) =>
|
||||
overlayActionsStore.publish(view),
|
||||
);
|
||||
// Initialize detection overlay BEFORE style stages so the composite
|
||||
// stage is first in the post-process pipeline. Both run on the engine,
|
||||
// Navara's too.
|
||||
@@ -415,7 +426,9 @@ export class ShellScene extends ShellFacade {
|
||||
this._layerBindings.disconnect();
|
||||
destroyTrackedReadout();
|
||||
destroyDetection();
|
||||
destroyWorldOverlay();
|
||||
destroyWorldOverlay(this._scene.engine);
|
||||
this._overlayActionsUnsubscribe?.();
|
||||
this._overlayActionsUnsubscribe = null;
|
||||
this.celestialRing?.destroy();
|
||||
this.celestialRing = null;
|
||||
this._visualSettings.destroy();
|
||||
|
||||
Vendored
+4
@@ -139,6 +139,7 @@ body {
|
||||
beneath the shared text/card canvas; both share one frame path. The root
|
||||
holds ONLY the card canvas — it is a stacking context (z-index below), and
|
||||
a canvas that blends against the scene cannot live inside one. */
|
||||
.world-overlay-root,
|
||||
#world-overlay-root {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -147,6 +148,8 @@ body {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.world-overlay-detection-surface,
|
||||
.world-overlay-canvas,
|
||||
#world-overlay-detection-surface,
|
||||
#world-overlay-canvas {
|
||||
position: absolute;
|
||||
@@ -179,6 +182,7 @@ body {
|
||||
`mix-blend-mode: screen` composites against the WebGL scene. z5 keeps it
|
||||
under the z6 root that carries the shared card canvas; do not move this
|
||||
surface inside #world-overlay-root, which would isolate the blend. */
|
||||
.world-overlay-detection-surface,
|
||||
#world-overlay-detection-surface {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
@@ -60,9 +60,6 @@ const vToastText = {
|
||||
|
||||
<template>
|
||||
<div id="cesiumContainer"></div>
|
||||
<div id="world-overlay-root" aria-hidden="true">
|
||||
<canvas id="world-overlay-canvas"></canvas>
|
||||
</div>
|
||||
<div id="world-overlay-actions" class="world-overlay-accessibility" role="region" :aria-label="t('Visible map targets')">
|
||||
<div id="world-overlay-action-list"><UiButton v-for="item in targets?.items ?? []" :key="item.key" :aria-label="item.label" :pressed="Boolean(item.selected)" :data-overlay-action-key="item.key" :onClick="onOverlayAction(targets.owner, item.key)">{{ item.label }}</UiButton></div>
|
||||
<div id="world-overlay-status" v-overlay-status="targets" role="status" aria-live="polite" aria-atomic="true"></div>
|
||||
|
||||
Reference in new issue
Block a user