From 09e2fd96a49e7f12563ef340ebc6943b4b76a3f0 Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Thu, 8 Oct 2026 15:16:29 +0400 Subject: [PATCH] refactor(street-level): no sequence image cache; one pose listener Co-Authored-By: Claude Opus 5.5 --- .../providers/mapillary/sequences.js | 22 ------------------- .../providers/mapillary/sequences.test.mjs | 19 +--------------- .../streetLevel/providers/mapillary/viewer.js | 19 ++++++++-------- src/layers/streetLevel/state.js | 2 -- 4 files changed, 11 insertions(+), 51 deletions(-) diff --git a/src/layers/streetLevel/providers/mapillary/sequences.js b/src/layers/streetLevel/providers/mapillary/sequences.js index a8166efb8..53cfe64e2 100644 --- a/src/layers/streetLevel/providers/mapillary/sequences.js +++ b/src/layers/streetLevel/providers/mapillary/sequences.js @@ -11,9 +11,6 @@ import { const SPRITE_ID = 'street-level:mapillary-cones'; -/** How many recently viewed sequences keep their image list in memory. */ -const SEQUENCE_CACHE_SIZE = 40; - /** Normalize a graph image record into the shape the cones use. */ function normalizeSequenceImage(record) { const coordinates = record?.geometry?.coordinates; @@ -115,14 +112,6 @@ export function createSequences({ state, source, parts }) { requestRender(); } - function remember(sequenceId, images) { - const { cache } = state.sequence; - cache.delete(sequenceId); - cache.set(sequenceId, images); - while (cache.size > SEQUENCE_CACHE_SIZE) - cache.delete(cache.keys().next().value); - } - /** Select a sequence: highlight its line and load its image cones. */ async function select(sequenceId) { if (!sequenceId || !state.viewer) return; @@ -135,16 +124,6 @@ export function createSequences({ state, source, parts }) { state.sequence.abort?.abort(); state.sequence.selectedId = sequenceId; parts.coverage.highlight(sequenceId); - const cached = state.sequence.cache.get(sequenceId); - if (cached) { - state.sequence.abort = null; - state.sequence.loading = false; - state.sequence.images = cached; - renderCones(cached); - state.sequence.error = null; - notify(); - return; - } // The previous sequence's cones must not stay clickable under the new // highlight while this one loads, nor after its load fails. clearCones(); @@ -164,7 +143,6 @@ export function createSequences({ state, source, parts }) { .filter(Boolean) .sort((a, b) => a.capturedAt - b.capturedAt), ); - remember(sequenceId, images); state.sequence.images = images; renderCones(images); state.sequence.error = null; diff --git a/src/layers/streetLevel/providers/mapillary/sequences.test.mjs b/src/layers/streetLevel/providers/mapillary/sequences.test.mjs index 3fd0f6f19..72f25ee45 100644 --- a/src/layers/streetLevel/providers/mapillary/sequences.test.mjs +++ b/src/layers/streetLevel/providers/mapillary/sequences.test.mjs @@ -67,7 +67,6 @@ function setup() { sequence: { selectedId: null, images: [], - cache: new Map(), collection: cones(), loading: false, error: null, @@ -112,7 +111,7 @@ test('a failed sequence load leaves no stale cones and can be retried', async () assert.equal(state.sequence.error, null, 'cleared once B loads'); }); -test('a late answer for a superseded sequence neither draws nor is cached', async () => { +test('a late answer for a superseded sequence draws nothing', async () => { const { state, source, sequences, drawn } = setup(); sequences.select('A'); sequences.select('B'); @@ -123,7 +122,6 @@ test('a late answer for a superseded sequence neither draws nor is cached', asyn await settle(); assert.deepEqual(drawn(), ['B-0', 'B-1']); assert.equal(state.sequence.selectedId, 'B'); - assert.equal(state.sequence.cache.has('A'), false); assert.equal(state.sequence.loading, false); }); @@ -135,21 +133,6 @@ test('a second click on a loading sequence does not start another lookup', async assert.equal(source.calls[0].signal.aborted, false); }); -test('a cached sequence is re-selected without a lookup', async () => { - const { state, source, sequences, drawn } = setup(); - sequences.select('A'); - source.last('A').resolve(records('A')); - await settle(); - sequences.select('B'); - source.last('B').resolve(records('B')); - await settle(); - sequences.select('A'); - assert.equal(source.calls.length, 2, 'A came from the cache'); - assert.deepEqual(drawn(), ['A-0', 'A-1'], 'drawn at once'); - assert.equal(state.sequence.loading, false); - assert.equal(state.sequence.selectedId, 'A'); -}); - test('clearSelection cancels the lookup and drops the highlight', async () => { const { state, source, sequences, highlights, drawn } = setup(); sequences.select('A'); diff --git a/src/layers/streetLevel/providers/mapillary/viewer.js b/src/layers/streetLevel/providers/mapillary/viewer.js index 36b6c4927..4d0aca717 100644 --- a/src/layers/streetLevel/providers/mapillary/viewer.js +++ b/src/layers/streetLevel/providers/mapillary/viewer.js @@ -27,7 +27,8 @@ export function createMapillaryViewer({ source, render } = {}) { let renderMode = 'letterbox'; /** Metadata of the image on screen; pov/position events reuse it. */ let current = null; - const listeners = new Set(); + /** The pose listener (the viewer host); one at a time. */ + let poseListener = null; function requestRender() { render?.governorRequestRender?.('mapillary-viewer'); @@ -52,12 +53,10 @@ export function createMapillaryViewer({ source, render } = {}) { } function emit(pose) { - for (const listener of [...listeners]) { - try { - listener(pose); - } catch { - /* listener errors are the core's to log */ - } + try { + poseListener?.(pose); + } catch { + /* listener errors are the core's to log */ } } @@ -201,8 +200,10 @@ export function createMapillaryViewer({ source, render } = {}) { }, onPose(listener) { - listeners.add(listener); - return () => listeners.delete(listener); + poseListener = listener; + return () => { + if (poseListener === listener) poseListener = null; + }; }, }; } diff --git a/src/layers/streetLevel/state.js b/src/layers/streetLevel/state.js index a62d8fecc..84c24fd11 100644 --- a/src/layers/streetLevel/state.js +++ b/src/layers/streetLevel/state.js @@ -71,8 +71,6 @@ export function createState({ services }) { sequence: { selectedId: null, images: [], - /** Recent sequences' thinned images, by sequence id. */ - cache: new Map(), collection: null, loading: false, /** Why the selected sequence's images could not load, until it goes. */