mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-12 06:12:18 +00:00
Keep the tracked aircraft through a failed engine switch on Cesium
Cesium follows a display's sprite through a proxy Entity that mirrors the native's readout fields, and gevRestoring was not one of them. A follow the flights layer resumed on attach therefore read as new navigation to the shell's tracked-entity watcher, which made the share restore of the same attach count itself superseded and clear the selection. That is why the aircraft was lost after a failed switch's rollback onto Cesium, and after the retry. The proxy now shows the restoring mark while its follow starts. The flights renderer test runs a detach, a failed attach to a candidate, the rollback and a retry on headless Cesium engines, and checks that the aircraft stays tracked and no navigation is stamped.
This commit is contained in:
1 parent
f06b93fbc9
commit
145adc08e0
3 files changed
+110
-1
No files matched your search
@@ -23,13 +23,18 @@ import { toCesium3 } from './convert.js';
|
|||||||
const MAX_FRAME_ATTEMPTS = 120;
|
const MAX_FRAME_ATTEMPTS = 120;
|
||||||
const entityName = (entity) => (entity ? entity.name || entity.id : undefined);
|
const entityName = (entity) => (entity ? entity.name || entity.id : undefined);
|
||||||
// What viewer.trackedEntity readers take from a tracked Entity, read through
|
// What viewer.trackedEntity readers take from a tracked Entity, read through
|
||||||
// a proxy from the layer's own handle.
|
// a proxy from the layer's own handle. gevRestoring marks a follow that
|
||||||
|
// rebinds the session's own subject (a resumed renderer, a share restore)
|
||||||
|
// while it starts: trackedEntityChanged readers (the shell's navigation
|
||||||
|
// stamp) must see it on the proxy, or a resumed follow supersedes the
|
||||||
|
// restore it belongs to.
|
||||||
const READOUT_FIELDS = [
|
const READOUT_FIELDS = [
|
||||||
'gevTrackedId',
|
'gevTrackedId',
|
||||||
'gevLabelModel',
|
'gevLabelModel',
|
||||||
'gevDisplayPosition',
|
'gevDisplayPosition',
|
||||||
'gevVisualPosition',
|
'gevVisualPosition',
|
||||||
'gevSelectionOrigin',
|
'gevSelectionOrigin',
|
||||||
|
'gevRestoring',
|
||||||
];
|
];
|
||||||
const ZOOM_INERTIA_STATES = new WeakMap();
|
const ZOOM_INERTIA_STATES = new WeakMap();
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,26 @@ test('a native that is not an Entity is echoed and followed through an ENU proxy
|
|||||||
assert.equal(viewer.entities.contains(proxy), false);
|
assert.equal(viewer.entities.contains(proxy), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("the proxy shows a restoring native's mark while its follow starts", () => {
|
||||||
|
const { viewer, camera } = setup();
|
||||||
|
const native = { id: 'flights-tracked-1', gevTrackedId: 'flights:abc123' };
|
||||||
|
// What trackedEntityChanged readers see as the follow starts.
|
||||||
|
const seen = [];
|
||||||
|
viewer.trackedEntityChanged.addEventListener((entity) =>
|
||||||
|
seen.push(entity?.gevRestoring),
|
||||||
|
);
|
||||||
|
native.gevRestoring = true;
|
||||||
|
const handle = camera.follow({
|
||||||
|
native,
|
||||||
|
position: () => TARGET,
|
||||||
|
viewFrom: () => ({ x: 0, y: -500, z: 200 }),
|
||||||
|
});
|
||||||
|
delete native.gevRestoring;
|
||||||
|
assert.deepEqual(seen, [true]);
|
||||||
|
assert.equal(viewer.trackedEntity.gevRestoring, undefined, 'read through');
|
||||||
|
handle.stop();
|
||||||
|
});
|
||||||
|
|
||||||
test('a camera command or another tracked entity ends the follow', () => {
|
test('a camera command or another tracked entity ends the follow', () => {
|
||||||
const { viewer, camera, changes } = setup();
|
const { viewer, camera, changes } = setup();
|
||||||
const options = { position: () => TARGET, viewFrom: { x: 0, y: -500, z: 0 } };
|
const options = { position: () => TARGET, viewFrom: { x: 0, y: -500, z: 0 } };
|
||||||
|
|||||||
@@ -293,3 +293,87 @@ test('the flights logic draws, tracks and follows on the Cesium engine through t
|
|||||||
assert.equal(engine.lines.list().length, 0, 'no line left behind');
|
assert.equal(engine.lines.list().length, 0, 'no line left behind');
|
||||||
engine.destroy();
|
engine.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a failed engine switch, its rollback and the retry re-follow the tracked aircraft as a restore', async () => {
|
||||||
|
const { createCesiumMoversEngine } =
|
||||||
|
await import('../../testSupport/cesiumMoversEngine.mjs');
|
||||||
|
const { createFlightsDisplay } = await import('./render/display.js');
|
||||||
|
const { aircraftAtlasLayout } = await import('../aircraft/render/display.js');
|
||||||
|
const layout = aircraftAtlasLayout();
|
||||||
|
// The shell's navigation stamp (LayerBindings.observeCamera): a follow
|
||||||
|
// Cesium announces through trackedEntityChanged is new navigation unless
|
||||||
|
// it rebinds the session's own subject. A new navigation supersedes the
|
||||||
|
// switch's share restore, which then clears the selection.
|
||||||
|
const navigations = [];
|
||||||
|
const cesium = () => {
|
||||||
|
const made = createCesiumMoversEngine();
|
||||||
|
made.engine.camera.setView({
|
||||||
|
destination: geo.cartesian3.fromDegrees(-97.5, 30.3, 400_000),
|
||||||
|
});
|
||||||
|
made.viewer.trackedEntityChanged.addEventListener((entity) => {
|
||||||
|
if (entity && !entity.gevRestoring)
|
||||||
|
navigations.push(entity.gevTrackedId);
|
||||||
|
});
|
||||||
|
return made;
|
||||||
|
};
|
||||||
|
const working = cesium();
|
||||||
|
let display = null;
|
||||||
|
const layer = createApplicationFlights({
|
||||||
|
surface: defaultSurface,
|
||||||
|
source: {
|
||||||
|
label: 'Fixture aircraft',
|
||||||
|
getSnapshot: async () => snapshotOf(['abc123', 'def456']),
|
||||||
|
},
|
||||||
|
militaryRegistry,
|
||||||
|
renderer: {
|
||||||
|
create: ({ engine: given }) =>
|
||||||
|
(display = createFlightsDisplay(given, {
|
||||||
|
drawAtlas: async () => ({
|
||||||
|
canvas: { width: layout.width, height: layout.height },
|
||||||
|
cells: layout.cells,
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
layer.init(working.viewer, { engine: working.engine });
|
||||||
|
layer.enable();
|
||||||
|
const candidate = createFakeEngine();
|
||||||
|
let retried = null;
|
||||||
|
try {
|
||||||
|
await display.whenReady();
|
||||||
|
await layer.update(null);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
// The person's own selection is navigation.
|
||||||
|
assert.equal(layer.trackById('def456', { origin: 'user' }), true);
|
||||||
|
assert.deepEqual(navigations, ['flights:def456']);
|
||||||
|
navigations.length = 0;
|
||||||
|
// The switch: the working engine detaches; the candidate attaches the
|
||||||
|
// layer, then fails and releases what it attached.
|
||||||
|
layer.detachRenderer(working.viewer);
|
||||||
|
layer.attachRenderer(null, { engine: candidate });
|
||||||
|
assert.equal(candidate.camera.following()?.label?.id, 'flights:def456');
|
||||||
|
layer.detachRenderer(null);
|
||||||
|
// The rollback: the working engine attaches again and follows the same
|
||||||
|
// aircraft, announced as a restore.
|
||||||
|
layer.attachRenderer(working.viewer, { engine: working.engine });
|
||||||
|
assert.equal(layer.getTrackedInfo()?.icao24, 'def456');
|
||||||
|
assert.equal(working.viewer.trackedEntity?.gevTrackedId, 'flights:def456');
|
||||||
|
assert.deepEqual(navigations, [], 'the rollback is not new navigation');
|
||||||
|
// The retry onto a new Cesium engine.
|
||||||
|
layer.detachRenderer(working.viewer);
|
||||||
|
retried = cesium();
|
||||||
|
layer.attachRenderer(retried.viewer, { engine: retried.engine });
|
||||||
|
assert.equal(layer.getTrackedInfo()?.icao24, 'def456');
|
||||||
|
assert.equal(retried.viewer.trackedEntity?.gevTrackedId, 'flights:def456');
|
||||||
|
assert.equal(
|
||||||
|
retried.engine.camera.following()?.label?.id,
|
||||||
|
'flights:def456',
|
||||||
|
);
|
||||||
|
assert.deepEqual(navigations, [], 'the retry is not new navigation');
|
||||||
|
} finally {
|
||||||
|
layer.disable();
|
||||||
|
layer.destroy(retried?.viewer ?? working.viewer);
|
||||||
|
}
|
||||||
|
for (const engine of [working.engine, candidate, retried?.engine])
|
||||||
|
engine?.destroy();
|
||||||
|
});
|
||||||
Reference in new issue
Block a user