Stop a Bhote Koshi attachment that resumes after its scene was released

This commit is contained in:
German Khokhlov committed 2026-11-03 14:47:35 +04:00
1 parent 2853938df7
commit baabff6acf
2 files changed
+77 -5

No files matched your search

+18 -5
View File
@@ -1177,7 +1177,7 @@ export function createBhoteKoshiEventLayer({
return _event; return _event;
} }
async function createImagery(event) { async function createImagery(event, current = () => true) {
const rectangle = Cesium.Rectangle.fromDegrees( const rectangle = Cesium.Rectangle.fromDegrees(
event.imagery.rectangle.west, event.imagery.rectangle.west,
event.imagery.rectangle.south, event.imagery.rectangle.south,
@@ -1194,13 +1194,13 @@ export function createBhoteKoshiEventLayer({
credit: 'Vantor WorldView Open Data · CC BY-NC 4.0', credit: 'Vantor WorldView Open Data · CC BY-NC 4.0',
}), }),
])); ]));
if (!_enabled) return; if (!_enabled || !current()) return;
if (_native) { if (_native) {
await _native.loadImages(event.imagery.rectangle, [ await _native.loadImages(event.imagery.rectangle, [
beforeProvider, beforeProvider,
afterProvider, afterProvider,
]); ]);
setSplit(_split); if (current()) setSplit(_split);
return; return;
} }
_imageryLayerCollection = _viewer.imageryLayers; _imageryLayerCollection = _viewer.imageryLayers;
@@ -3516,7 +3516,15 @@ export function createBhoteKoshiEventLayer({
_imageryLayerCollection = null; _imageryLayerCollection = null;
} }
async function attachRenderer(viewer, { engine = null } = {}) { // Each attachment and release advances this. An attachment that resumes
// after a release, or after its scene signal aborted, stops right there;
// the release that follows it owns whatever it had already created.
let _attachGeneration = 0;
async function attachRenderer(viewer, { engine = null, signal = null } = {}) {
const generation = ++_attachGeneration;
const current = () =>
generation === _attachGeneration && signal?.aborted !== true;
_viewer = viewer; _viewer = viewer;
_engine = engine; _engine = engine;
if (!_viewer && _engine) _native = createNativeRenderer(engine); if (!_viewer && _engine) _native = createNativeRenderer(engine);
@@ -3532,16 +3540,20 @@ export function createBhoteKoshiEventLayer({
: 'esri', : 'esri',
}); });
await _comparisonLease.ready; await _comparisonLease.ready;
if (!current()) return;
} }
await createImagery(_event); await createImagery(_event, current);
if (!current()) return;
if (_floodDataSource) { if (_floodDataSource) {
if (_native) _nativeFlood = _native.draw(_floodDataSource); if (_native) _nativeFlood = _native.draw(_floodDataSource);
else await _viewer.dataSources.add(_floodDataSource); else await _viewer.dataSources.add(_floodDataSource);
if (!current()) return;
} }
registerDynamicCredit(engine ?? viewer, BHOTE_KOSHI_CREDIT); registerDynamicCredit(engine ?? viewer, BHOTE_KOSHI_CREDIT);
_scenePathPositionCache.clear(); _scenePathPositionCache.clear();
_lastCinematicProgress = null; _lastCinematicProgress = null;
await syncTerrainComparison(); await syncTerrainComparison();
if (!current()) return;
createSplitControl(_event); createSplitControl(_event);
syncPresentationMode(); syncPresentationMode();
attachCameraInputRelease(); attachCameraInputRelease();
@@ -3551,6 +3563,7 @@ export function createBhoteKoshiEventLayer({
} }
async function detachRenderer() { async function detachRenderer() {
++_attachGeneration;
detachCameraInputRelease(); detachCameraInputRelease();
detachEvidenceCardActivation(); detachEvidenceCardActivation();
_splitControl?.destroy(); _splitControl?.destroy();
+59
View File
@@ -2992,3 +2992,62 @@ test('Bhote Koshi corridor remains ordered downstream and inside the imagery rec
priorChainage = point.chainageM; priorChainage = point.chainageM;
} }
}); });
for (const stop of ['detach', 'scene-abort']) {
test(`Bhote Koshi attach resumed after a ${stop} leaves the next scene untouched`, async () => {
const event = JSON.parse(await readFile(eventUrl, 'utf8'));
const originalDocument = globalThis.document;
const originalWindow = globalThis.window;
installEventDom();
globalThis.window = { setTimeout: () => 1, clearTimeout() {} };
const first = eventViewer();
const second = eventViewer();
const layer = createBhoteKoshiEventLayer({
eventLoader: async () => event,
imageryProviderFactory: async (url) => ({ url }),
embeddedMediaFactory: () => ({ show() {}, hide() {}, pause() {}, destroy() {} }),
overlayHost: { setEntries() {}, setVisible() {}, clearSource() {} },
renderHost: { request() {}, hold() {}, release() {} },
});
const stack = (initial, gate = null) => {
let active = initial;
let generation = 1;
return {
getActiveId: () => active,
getSwitchGeneration: () => generation,
async setStack(id) {
await gate;
active = id;
generation += 1;
},
};
};
layer.attachMapStackController(stack('esri-imagery'));
const manager = new LayerLifecycle(first);
manager.register(layer);
try {
assert.equal(await manager.setEnabled(layer.id, true, { origin: 'local-restore' }), true);
await layer.detachRenderer();
// The incoming map has to switch first: the attachment waits on it.
let openGate;
const gate = new Promise((resolve) => { openGate = resolve; });
layer.attachMapStackController(stack('photoreal', gate));
const scene = new AbortController();
const attaching = layer.attachRenderer(second, { signal: scene.signal });
let detaching = null;
if (stop === 'detach') detaching = layer.detachRenderer();
else scene.abort();
openGate();
await attaching;
await detaching;
assert.deepEqual(second._test.imagery, []);
assert.deepEqual(second._test.dataSources, []);
if (stop === 'scene-abort') await layer.detachRenderer();
} finally {
await manager.destroyLayer(layer.id);
globalThis.document = originalDocument;
globalThis.window = originalWindow;
}
});
}