mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-09 21:02:19 +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 entityName = (entity) => (entity ? entity.name || entity.id : undefined);
|
||||
// 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 = [
|
||||
'gevTrackedId',
|
||||
'gevLabelModel',
|
||||
'gevDisplayPosition',
|
||||
'gevVisualPosition',
|
||||
'gevSelectionOrigin',
|
||||
'gevRestoring',
|
||||
];
|
||||
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);
|
||||
});
|
||||
|
||||
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', () => {
|
||||
const { viewer, camera, changes } = setup();
|
||||
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');
|
||||
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