diff --git a/scripts/package-boundaries.json b/scripts/package-boundaries.json index 1450306f9..ae9d2e2dd 100644 --- a/scripts/package-boundaries.json +++ b/scripts/package-boundaries.json @@ -2045,6 +2045,8 @@ "src/layers/submarineCables/index.js", "src/layers/submarineCables/ingestion.js", "src/layers/submarineCables/interaction.js", + "src/layers/submarineCables/pickState.js", + "src/data/entityRecords.js", "src/layers/submarineCables/lifecycle.js", "src/layers/submarineCables/overlay.js", "src/layers/submarineCables/policy.js", @@ -2622,6 +2624,7 @@ "src/layers/submarineCables/index.js", "src/layers/submarineCables/ingestion.js", "src/layers/submarineCables/interaction.js", + "src/layers/submarineCables/pickState.js", "src/layers/submarineCables/lifecycle.js", "src/layers/submarineCables/overlay.js", "src/layers/submarineCables/policy.js", @@ -3529,6 +3532,7 @@ "src/layers/submarineCables/index.js", "src/layers/submarineCables/ingestion.js", "src/layers/submarineCables/interaction.js", + "src/layers/submarineCables/pickState.js", "src/layers/submarineCables/lifecycle.js", "src/layers/submarineCables/overlay.js", "src/layers/submarineCables/policy.js", diff --git a/src/data/telegeographySubmarineCables.test.mjs b/src/data/telegeographySubmarineCables.test.mjs index 110f65f18..d1373be44 100644 --- a/src/data/telegeographySubmarineCables.test.mjs +++ b/src/data/telegeographySubmarineCables.test.mjs @@ -22,6 +22,7 @@ import { selectCableReferenceLabelWinners, updateCableReferenceStem, } from './telegeographySubmarineCables.js'; +import { cablePicks } from '../layers/submarineCables/pickState.js'; test('cable reference winners remain nearest-first and hard-capped', () => { const records = Array.from({ length: 300 }, (_, index) => ({ @@ -1840,7 +1841,7 @@ test('multipart association accepts frozen provider collections without modifyin for (const dataSource of env.dataSources.slice(0, 2)) { assert.deepEqual( dataSource.entities.values.map( - (entity) => entity.__gevTeleGeography?.label, + (entity) => cablePicks.read(entity)?.info.label, ), ['Alpha cable', 'Alpha cable', 'Alpha cable', 'Beta cable'], ); @@ -1866,7 +1867,7 @@ test('multipart cables retain their own styling, picking and culling on every se assert.equal(entities.length, 4); for (const [index, entity] of entities.entries()) { const feature = features[index < 3 ? 0 : 1]; - const info = entity.__gevTeleGeography; + const info = cablePicks.read(entity)?.info; assert.equal(info?.label, feature.properties.name, entity.id); assert.deepEqual(info.reference, { lon: feature.properties.coordinates[0], @@ -1922,7 +1923,7 @@ test('multipart association supports missing and repeated IDs without guessing C ['alpha', 'alpha_2', 'alpha_3', 'alpha_2_2', 'cable-2', 'alpha_4'], ); assert.deepEqual( - entities.map((entity) => entity.__gevTeleGeography?.label), + entities.map((entity) => cablePicks.read(entity)?.info.label), [ 'Alpha cable', 'Alpha cable', @@ -1934,7 +1935,7 @@ test('multipart association supports missing and repeated IDs without guessing C ); assert.deepEqual( env.dataSources[2].entities.values.map( - (entity) => entity.__gevTeleGeography.featureId, + (entity) => cablePicks.read(entity)?.info.featureId, ), ['alpha', 'alpha_2', 'cable-2', 'alpha'], ); @@ -1951,12 +1952,12 @@ test('multipart landing geometry keeps every pick with one reference per feature try { const entities = env.dataSources[1].entities.values; assert.deepEqual( - entities.map((entity) => entity.__gevTeleGeography?.label), + entities.map((entity) => cablePicks.read(entity)?.info.label), ['Alpha cable', 'Alpha cable', 'Alpha cable', 'Beta cable'], ); assert.ok( entities.every( - (entity) => entity.__gevTeleGeography.kind === 'landing-point', + (entity) => cablePicks.read(entity)?.info.kind === 'landing-point', ), ); assert.equal(env.dataSources[2].entities.values.length, 2); @@ -1989,11 +1990,11 @@ test('every bundled cable segment is associated with its source feature', async for (const [index, entity] of entities.entries()) { const feature = expandedFeatures[index]; assert.equal( - entity.__gevTeleGeography?.label, + cablePicks.read(entity)?.info.label, feature.properties.name, entity.id, ); - assert.deepEqual(entity.__gevTeleGeography.reference, { + assert.deepEqual(cablePicks.read(entity)?.info.reference, { lon: feature.properties.coordinates[0], lat: feature.properties.coordinates[1], }); diff --git a/src/layers/submarineCables/ingestion.js b/src/layers/submarineCables/ingestion.js index 714b8753b..23bc4c735 100644 --- a/src/layers/submarineCables/ingestion.js +++ b/src/layers/submarineCables/ingestion.js @@ -142,7 +142,6 @@ export function createIngestion({ state, parts, source }) { const cableEntities = state._cableDataSource.entities.values; const landingEntities = state._landingDataSource.entities.values; - state._pickByEntity = new WeakMap(); state._referenceRecords = []; state._surfaceRecords = []; diff --git a/src/layers/submarineCables/interaction.js b/src/layers/submarineCables/interaction.js index aaa4f8aad..19500db6d 100644 --- a/src/layers/submarineCables/interaction.js +++ b/src/layers/submarineCables/interaction.js @@ -1,10 +1,10 @@ import * as Cesium from 'cesium'; import { isPointerFree } from '../../data/inputOwnership.js'; +import { cablePicks } from './pickState.js'; export function createInteraction({ state, screenSpaceEventHandlerFactory }) { function registerPickEntity(entity, info) { - entity.__gevTeleGeography = info; - state._pickByEntity.set(entity, info); + cablePicks.write(entity).info = info; } function beginInteraction(viewer) { @@ -25,9 +25,8 @@ export function createInteraction({ state, screenSpaceEventHandlerFactory }) { if (!picked) return null; const primitive = picked.primitive; if (primitive) { - const primitiveInfo = state._pickByEntity.get(primitive); + const primitiveInfo = cablePicks.read(primitive)?.info; if (primitiveInfo) return primitiveInfo; - if (primitive.__gevTeleGeography) return primitive.__gevTeleGeography; if ( primitive.id && typeof primitive.id === 'object' && @@ -39,9 +38,8 @@ export function createInteraction({ state, screenSpaceEventHandlerFactory }) { const entity = picked.id; if (entity) { - const entityInfo = state._pickByEntity.get(entity); + const entityInfo = cablePicks.read(entity)?.info; if (entityInfo) return entityInfo; - if (entity.__gevTeleGeography) return entity.__gevTeleGeography; if (entity.id && typeof entity.id === 'object' && entity.id.reference) { return entity.id; } diff --git a/src/layers/submarineCables/lifecycle.js b/src/layers/submarineCables/lifecycle.js index 39e41f630..87da52ee7 100644 --- a/src/layers/submarineCables/lifecycle.js +++ b/src/layers/submarineCables/lifecycle.js @@ -133,7 +133,6 @@ export function createLifecycle({ state, parts, source, mapStackEventTarget }) { state._referenceDataSource = null; state._referenceRecords = []; state._surfaceRecords = []; - state._pickByEntity = new WeakMap(); state._referenceLabelCount = 0; state._publishScratch.length = 0; state._markerBlendDone = false; diff --git a/src/layers/submarineCables/pickState.js b/src/layers/submarineCables/pickState.js new file mode 100644 index 000000000..c84e592bb --- /dev/null +++ b/src/layers/submarineCables/pickState.js @@ -0,0 +1,7 @@ +import { createEntityStore } from '../../data/entityRecords.js'; + +/** + * The reference a picked cable, landing point or reference marker flies to: + * `info` is `{ label, kind, featureId, reference: { lon, lat } }`. + */ +export const cablePicks = createEntityStore(); diff --git a/src/layers/submarineCables/rendering.js b/src/layers/submarineCables/rendering.js index 91936573f..15f1fd263 100644 --- a/src/layers/submarineCables/rendering.js +++ b/src/layers/submarineCables/rendering.js @@ -10,6 +10,7 @@ import { selectCableReferenceLabelWinners, cableReferencePriority, } from './overlay.js'; +import { cablePicks } from './pickState.js'; export function createRendering({ state }) { function resetPublishSignature() { @@ -40,7 +41,6 @@ export function createRendering({ state }) { state._referenceDataSource = null; state._referenceRecords = []; state._surfaceRecords = []; - state._pickByEntity = new WeakMap(); state._referenceLabelCount = 0; state._publishScratch.length = 0; // The blend pass targets the collections being dropped; the rebuilt @@ -180,8 +180,7 @@ export function createRendering({ state }) { }, }); - entity.__gevTeleGeography = info; - state._pickByEntity.set(entity, info); + cablePicks.write(entity).info = info; const record = { id: entity.id, entity, diff --git a/src/layers/submarineCables/state.js b/src/layers/submarineCables/state.js index 0761688aa..885cd722f 100644 --- a/src/layers/submarineCables/state.js +++ b/src/layers/submarineCables/state.js @@ -69,8 +69,6 @@ export function createState({ sweepClock, overlayHost }) { state._markerBlendInvariantWarned = false; - state._pickByEntity = new WeakMap(); - state._referenceLabelCount = 0; state._referenceSweepGate = createCableReferenceSweepGate({ diff --git a/src/voice/gevActions.js b/src/voice/gevActions.js index 70636dd4e..6ab688456 100644 --- a/src/voice/gevActions.js +++ b/src/voice/gevActions.js @@ -2869,9 +2869,12 @@ async function basemapLabelsAt( }; } +/** Viewers that already prewarm view targets on camera settle. */ +const prewarmedViewers = new WeakSet(); + function installViewTargetPrewarm(viewer) { - if (viewer.__gevViewTargetPrewarmInstalled) return; - viewer.__gevViewTargetPrewarmInstalled = true; + if (prewarmedViewers.has(viewer)) return; + prewarmedViewers.add(viewer); let timer = null; let reportedPrewarmFailure = false; viewer.camera.moveEnd.addEventListener(() => {