From 48988015f1470a3571c44f5ea227988a5ec84356 Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Sat, 7 Nov 2026 01:28:51 +0400 Subject: [PATCH] Give each engine its own extensions in place of the navaraViews map What only one engine has now travels with the engine instead of a WeakMap in navaraMode.js: engine.extensions.navara holds Navara's view, its SDK classes and the daylight reset, engine.extensions.cesium the Cesium viewer. The contract documents the field as outside the contract, and the coupling report counts its reads outside src/globe in place of the navaraViews reads (27, unchanged). --- scripts/engine-coupling-baseline.json | 2 +- scripts/report-engine-coupling.mjs | 9 ++-- src/app/navaraMode.js | 73 +++++++++++--------------- src/globe/cesium/createCesiumEngine.js | 2 + src/globe/contract.js | 5 ++ src/globe/navara/index.js | 17 ++++++ 6 files changed, 61 insertions(+), 47 deletions(-) diff --git a/scripts/engine-coupling-baseline.json b/scripts/engine-coupling-baseline.json index 71e03a271..e9af437a7 100644 --- a/scripts/engine-coupling-baseline.json +++ b/scripts/engine-coupling-baseline.json @@ -6,7 +6,7 @@ "viewerLines": 2424, "navaraKindChecks": 17, "engineSniffCalls": 51, - "navaraViewsReads": 27, + "engineExtensionReads": 27, "groundEffectPasses": 9, "cesiumSdkLoads": 6, "screenSpaceHandlerFiles": 20, diff --git a/scripts/report-engine-coupling.mjs b/scripts/report-engine-coupling.mjs index 35e600164..3f77cbde6 100644 --- a/scripts/report-engine-coupling.mjs +++ b/scripts/report-engine-coupling.mjs @@ -103,13 +103,14 @@ export const ENGINE_COUPLING_METRICS = Object.freeze([ ), }, { - id: 'navaraViewsReads', - label: 'Raw Navara reads through navaraViews', + id: 'engineExtensionReads', + label: 'Reads of engine.extensions (cesium, navara) outside src/globe', measure: (files) => files - .filter(({ file }) => !inEngine(file)) + .filter(({ file }) => !inGlobe(file)) .reduce( - (sum, { text }) => sum + countMatches(text, /\bnavaraViews\.get\(/), + (sum, { text }) => + sum + countMatches(text, /\bextensions\??\.(?:navara|cesium)\b/), 0, ), }, diff --git a/src/app/navaraMode.js b/src/app/navaraMode.js index 30bbc6cb7..360dea3d6 100644 --- a/src/app/navaraMode.js +++ b/src/app/navaraMode.js @@ -26,9 +26,6 @@ import { } from './navaraTools.js'; import { createRecentImageryPanel } from '../ui/recentImagery.js'; -// Native mesh capability stays at the composition boundary. -const navaraViews = new WeakMap(); - /** * An imported file's data source drawn on `engine` (UTILITIES ▸ Import); the * drawing loads on first use, like the layers' Navara renderers. @@ -37,7 +34,7 @@ async function drawImported(engine, dataSource) { const { drawImportedSource, measureImportImages } = await import('../layers/shared/render/navaraStory.js'); const sizes = await measureImportImages(dataSource); - return drawImportedSource(engine, navaraViews.get(engine), dataSource, { + return drawImportedSource(engine, engine.extensions.navara, dataSource, { sizes, }); } @@ -102,7 +99,7 @@ export async function registerNavaraLayerRenderers() { return (engine, options) => createRecentImageryNavaraRenderer(engine, { ...options, - ...navaraViews.get(engine), + ...engine.extensions.navara, }); }, transit: async () => { @@ -111,7 +108,7 @@ export async function registerNavaraLayerRenderers() { return (engine, options) => createTransitNavaraRenderer(engine, { ...options, - ...navaraViews.get(engine), + ...engine.extensions.navara, }); }, cctv: async () => { @@ -120,7 +117,7 @@ export async function registerNavaraLayerRenderers() { return (engine, options) => createCctvNavaraRenderer(engine, { ...options, - ...navaraViews.get(engine), + ...engine.extensions.navara, }); }, 'alpr-cameras': async () => @@ -141,7 +138,7 @@ export async function registerNavaraLayerRenderers() { return (engine, options) => createWeatherNavaraRenderer( engine, - navaraViews.get(engine), + engine.extensions.navara, options, ); }, @@ -151,42 +148,42 @@ export async function registerNavaraLayerRenderers() { const { createNepalEventDisplay } = await import('../layers/bhoteKoshi/render/navara.js'); return (engine) => - createNepalEventDisplay(engine, navaraViews.get(engine)); + createNepalEventDisplay(engine, engine.extensions.navara); }, 'bhote-koshi-locator': async () => { const { drawStorySource } = await import('../layers/shared/render/navaraStory.js'); return (engine, { data }) => - drawStorySource(engine, navaraViews.get(engine), data); + drawStorySource(engine, engine.extensions.navara, data); }, 'street-level': async () => { const { createStreetMap } = await import('../layers/streetLevel/render/navara.js'); - return (engine) => createStreetMap(engine, navaraViews.get(engine)); + return (engine) => createStreetMap(engine, engine.extensions.navara); }, traffic: async () => { const { createTrafficNavaraRenderer } = await import('../layers/traffic/render/navara.js'); return (engine) => - createTrafficNavaraRenderer(engine, navaraViews.get(engine)); + createTrafficNavaraRenderer(engine, engine.extensions.navara); }, directions: async () => { const { createDirectionsNavaraRenderer } = await import('../layers/directions/render/navara.js'); return (engine) => - createDirectionsNavaraRenderer(engine, navaraViews.get(engine)); + createDirectionsNavaraRenderer(engine, engine.extensions.navara); }, 'telegeography-submarine-cables': async () => { const { createNavaraCables } = await import('../layers/submarineCables/render/navara.js'); return (engine, { overlayHost }) => - createNavaraCables(engine, navaraViews.get(engine), overlayHost); + createNavaraCables(engine, engine.extensions.navara, overlayHost); }, wind: async () => { const { createWindNavaraRenderer } = await import('../layers/wind/render/navara.js'); return (engine) => - createWindNavaraRenderer(engine, navaraViews.get(engine)); + createWindNavaraRenderer(engine, engine.extensions.navara); }, 'weather-cyclones': async () => { const { createCyclonesNavaraRenderer } = @@ -194,7 +191,7 @@ export async function registerNavaraLayerRenderers() { return (engine, options) => createCyclonesNavaraRenderer( engine, - navaraViews.get(engine).view, + engine.extensions.navara.view, options, ); }, @@ -207,7 +204,7 @@ export async function registerNavaraLayerRenderers() { return (engine, { config, services }) => createInfrastructureNavaraRenderer( engine, - navaraViews.get(engine), + engine.extensions.navara, config, services, ); @@ -218,13 +215,13 @@ export async function registerNavaraLayerRenderers() { const { createGhostMapsNavaraRenderer } = await import('../layers/ghostMaps/render/navara.js'); return (engine) => - createGhostMapsNavaraRenderer(engine, navaraViews.get(engine).view); + createGhostMapsNavaraRenderer(engine, engine.extensions.navara.view); }, 'military-awareness': async () => { const { createAwarenessNavaraRenderer } = await import('../layers/awareness/render/navara.js'); return (engine) => - createAwarenessNavaraRenderer(engine, navaraViews.get(engine)); + createAwarenessNavaraRenderer(engine, engine.extensions.navara); }, 'military-installations': async () => { const { createInstallationsNavaraRenderer } = @@ -232,7 +229,7 @@ export async function registerNavaraLayerRenderers() { return (engine, options) => createInstallationsNavaraRenderer(engine, { ...options, - ...navaraViews.get(engine), + ...engine.extensions.navara, }); }, 'local-firms': async () => { @@ -241,7 +238,7 @@ export async function registerNavaraLayerRenderers() { return (engine, options) => createFirmsNavaraRenderer(engine, { ...options, - ...navaraViews.get(engine), + ...engine.extensions.navara, }); }, 'osm-places': async () => @@ -251,25 +248,25 @@ export async function registerNavaraLayerRenderers() { const { createDroneAirspaceNavaraRenderer } = await import('../layers/droneAirspace/render/navara.js'); return (engine) => - createDroneAirspaceNavaraRenderer(engine, navaraViews.get(engine)); + createDroneAirspaceNavaraRenderer(engine, engine.extensions.navara); }, 'fire-perimeters': async () => { const { createPerimetersNavaraRenderer } = await import('../layers/perimeters/render/navara.js'); return (engine) => - createPerimetersNavaraRenderer(engine, navaraViews.get(engine)); + createPerimetersNavaraRenderer(engine, engine.extensions.navara); }, 'gnss-integrity': async () => { const { createGnssNavaraRenderer } = await import('../layers/gnss/render/navara.js'); return (engine) => - createGnssNavaraRenderer(engine, navaraViews.get(engine)); + createGnssNavaraRenderer(engine, engine.extensions.navara); }, 'air-quality': async () => { const { createAirQualityNavaraRenderer } = await import('../layers/airQuality/render/navara.js'); return (engine) => - createAirQualityNavaraRenderer(engine, navaraViews.get(engine)); + createAirQualityNavaraRenderer(engine, engine.extensions.navara); }, earthquakes: async () => { const { createEarthquakesNavaraRenderer } = @@ -277,7 +274,7 @@ export async function registerNavaraLayerRenderers() { return (engine, options) => createEarthquakesNavaraRenderer(engine, { ...options, - ...navaraViews.get(engine), + ...engine.extensions.navara, }); }, 'rocket-launches': async () => { @@ -286,7 +283,7 @@ export async function registerNavaraLayerRenderers() { return (engine, options) => createLaunchesNavaraRenderer(engine, { ...options, - ...navaraViews.get(engine), + ...engine.extensions.navara, }); }, satellites: async () => @@ -352,17 +349,6 @@ export async function createNavaraScene({ }); defer(() => globe.destroy()); const { engine } = globe; - navaraViews.set(engine, { - view: globe.view, - alignDaylightToRestoredView: globe.alignDaylightToRestoredView, - MeshDesc: globe.MeshDesc, - NavaraColor: globe.NavaraColor, - EffectDesc: globe.LayerEffectDesc, - NavaraEffect: globe.NavaraEffect, - DrapedMesh: globe.DrapedMesh, - setupMaterialForDrape: globe.setupMaterialForDrape, - }); - defer(() => navaraViews.delete(engine)); // The layers drawn on Navara (their render/navara.js), before the catalog // asks the registry which layers this engine shows. await registerNavaraLayerRenderers(); @@ -400,7 +386,7 @@ export async function createNavaraScene({ const packGraphics = () => (packRenderer ??= createNavaraPackRenderer( engine, - navaraViews.get(engine), + engine.extensions.navara, )); return { viewer: null, @@ -446,8 +432,7 @@ export function attachViewerlessTools( ) { const { dataManager } = data; void styleManager.initialRestorePromise?.then(() => { - if (!signal.aborted) - navaraViews.get(engine)?.alignDaylightToRestoredView?.(); + if (!signal.aborted) engine.extensions.navara.alignDaylightToRestoredView(); }); const recentImagery = dataManager.layers.get('recent-imagery')?.module; if (recentImagery) { @@ -500,7 +485,11 @@ export function attachViewerlessTools( defer(() => styleManager._clearPersistentStatusNotice?.()); // The page session's annotation board, Draw and Import on this engine // (src/app/navaraTools.js). - const tools = { engine, views: navaraViews.get(engine), mapStackController }; + const tools = { + engine, + views: engine.extensions?.navara, + mapStackController, + }; const context = { operations, placeSearch, diff --git a/src/globe/cesium/createCesiumEngine.js b/src/globe/cesium/createCesiumEngine.js index 5a9d72e32..5d2a976ae 100644 --- a/src/globe/cesium/createCesiumEngine.js +++ b/src/globe/cesium/createCesiumEngine.js @@ -82,6 +82,8 @@ export function createCesiumEngine(viewer, options = {}) { let destroyed = false; return { kind: 'cesium', + // What only Cesium has, for code still written against the viewer. + extensions: Object.freeze({ cesium: Object.freeze({ viewer }) }), host: { container: viewer.container, canvas, diff --git a/src/globe/contract.js b/src/globe/contract.js index f817cf4f3..7d82aa497 100644 --- a/src/globe/contract.js +++ b/src/globe/contract.js @@ -482,6 +482,11 @@ * @property {GlobeLines} lines The layers' dynamic polylines. * @property {() => void} destroy * @property {() => boolean} isDestroyed True once destroy() has run. + * @property {{ cesium?: object, navara?: object }} [extensions] + * What only one engine has, under its kind: Cesium's Viewer, Navara's view + * and SDK classes. Not part of the contract: only code written against that + * engine reads it, and scripts/report-engine-coupling.mjs counts each read + * outside src/globe. */ export const ENGINE_KINDS = Object.freeze(['cesium', 'navara']); diff --git a/src/globe/navara/index.js b/src/globe/navara/index.js index a0217e77e..605f8eec1 100644 --- a/src/globe/navara/index.js +++ b/src/globe/navara/index.js @@ -248,6 +248,23 @@ export async function createNavaraGlobe({ container, search, hd = false }) { throw error; } let destroyed = false; + // What only Navara has, for the code written against Navara itself (its + // layer renderers): the view, the SDK classes and the daylight reset. + engine.extensions = Object.freeze({ + navara: Object.freeze({ + view, + MeshDesc, + NavaraColor, + EffectDesc: layerEffectDesc(EffectDesc), + NavaraEffect, + DrapedMesh, + setupMaterialForDrape, + alignDaylightToRestoredView() { + look.resetDaylight(); + engine.render.request('restored-daylight'); + }, + }), + }); return { view, engine,