mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-11 22:02:21 +00:00
Keep the debug handle off window between engines and recover the page after a failed recovery
The application's debug handle is one object, present on window only while a globe is attached, so scripts reading __godsEyeView?.engine.kind see no null engine mid-switch. A detached application has no live components: settle no longer waits for a frame of a missing engine, and an engine that runs again after a failed recovery hides the loading screen the error showed.
This commit is contained in:
1 parent
a99b862580
commit
8e1a3bab3e
3 files changed
+104
-6
No files matched your search
@@ -0,0 +1,89 @@
|
|||||||
|
// The debug handle is one object for the page; it is on window only while a
|
||||||
|
// globe is attached, as when every engine built its own application, so
|
||||||
|
// scripts reading `__godsEyeView?.engine.kind` never see a null engine.
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { createApplicationTools } from './tools.js';
|
||||||
|
import { createSceneHost } from './sceneHost.js';
|
||||||
|
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||||
|
import { _resetRenderGovernorForTest } from '../renderGovernor.js';
|
||||||
|
|
||||||
|
test('the debug handle leaves window between engines and comes back the same', async (t) => {
|
||||||
|
const saved = {
|
||||||
|
window: globalThis.window,
|
||||||
|
document: globalThis.document,
|
||||||
|
localStorage: globalThis.localStorage,
|
||||||
|
};
|
||||||
|
globalThis.window = {};
|
||||||
|
globalThis.localStorage = {
|
||||||
|
getItem: () => null,
|
||||||
|
setItem() {},
|
||||||
|
removeItem() {},
|
||||||
|
};
|
||||||
|
globalThis.document = {
|
||||||
|
hidden: false,
|
||||||
|
getElementById: () => null,
|
||||||
|
querySelector: () => null,
|
||||||
|
body: {
|
||||||
|
classList: { add() {}, remove() {}, toggle() {}, contains: () => false },
|
||||||
|
appendChild() {},
|
||||||
|
},
|
||||||
|
addEventListener() {},
|
||||||
|
removeEventListener() {},
|
||||||
|
};
|
||||||
|
t.after(() => {
|
||||||
|
_resetRenderGovernorForTest();
|
||||||
|
for (const [name, value] of Object.entries(saved)) {
|
||||||
|
if (value === undefined) delete globalThis[name];
|
||||||
|
else globalThis[name] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const sceneHost = createSceneHost();
|
||||||
|
const navara = (kind) => ({
|
||||||
|
kind,
|
||||||
|
viewer: null,
|
||||||
|
engine: createFakeEngine({ kind, canvas: new EventTarget() }),
|
||||||
|
tileset: null,
|
||||||
|
mapStackController: { kind: 'maps' },
|
||||||
|
operations: { surface: {} },
|
||||||
|
});
|
||||||
|
let current = navara('navara');
|
||||||
|
await sceneHost.attach(current);
|
||||||
|
const scene = {
|
||||||
|
operations: { surface: {} },
|
||||||
|
sceneHost,
|
||||||
|
current: () => current,
|
||||||
|
};
|
||||||
|
const cleanups = [];
|
||||||
|
await createApplicationTools({
|
||||||
|
scene,
|
||||||
|
voice: {
|
||||||
|
createControl: () => ({ update() {}, on: () => () => {}, remove() {} }),
|
||||||
|
createCard: () => null,
|
||||||
|
createSession: () => ({
|
||||||
|
async start() {},
|
||||||
|
stop() {},
|
||||||
|
sendText() {},
|
||||||
|
sendMapEvent() {},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
controls: { styleManager: {}, weatherEffects: null },
|
||||||
|
data: {
|
||||||
|
dataManager: { layers: new Map(), getAll: () => [] },
|
||||||
|
presentation: { flushVisible() {} },
|
||||||
|
},
|
||||||
|
signal: new AbortController().signal,
|
||||||
|
defer: (dispose) => cleanups.push(dispose),
|
||||||
|
});
|
||||||
|
const handle = window.__godsEyeView;
|
||||||
|
assert.equal(handle.engine, current.engine);
|
||||||
|
await sceneHost.detach({ handoff: true });
|
||||||
|
assert.equal(window.__godsEyeView, undefined);
|
||||||
|
assert.equal(handle.engine, null);
|
||||||
|
current = navara('navara');
|
||||||
|
await sceneHost.attach(current);
|
||||||
|
assert.equal(window.__godsEyeView, handle);
|
||||||
|
assert.equal(handle.engine, current.engine);
|
||||||
|
while (cleanups.length) await cleanups.pop()();
|
||||||
|
assert.equal(window.__godsEyeView, undefined);
|
||||||
|
});
|
||||||
+6
-2
@@ -162,7 +162,8 @@ export async function createApplicationTools({
|
|||||||
{ defer },
|
{ defer },
|
||||||
);
|
);
|
||||||
|
|
||||||
// The application's debug handle; its globe fields follow the engine.
|
// The application's debug handle, the same object on every globe; its
|
||||||
|
// globe fields follow the engine.
|
||||||
const debug = {
|
const debug = {
|
||||||
viewer: null,
|
viewer: null,
|
||||||
engine: null,
|
engine: null,
|
||||||
@@ -179,7 +180,6 @@ export async function createApplicationTools({
|
|||||||
surfaceServices: operations.surface,
|
surfaceServices: operations.surface,
|
||||||
requestRender: governorRequestRender,
|
requestRender: governorRequestRender,
|
||||||
};
|
};
|
||||||
window.__godsEyeView = debug;
|
|
||||||
defer(() => {
|
defer(() => {
|
||||||
if (window.__godsEyeView === debug) delete window.__godsEyeView;
|
if (window.__godsEyeView === debug) delete window.__godsEyeView;
|
||||||
});
|
});
|
||||||
@@ -232,8 +232,12 @@ export async function createApplicationTools({
|
|||||||
cockpitCloudEffects: controls.cockpitCloudEffects ?? null,
|
cockpitCloudEffects: controls.cockpitCloudEffects ?? null,
|
||||||
voiceCommands: attached.voiceCommands,
|
voiceCommands: attached.voiceCommands,
|
||||||
});
|
});
|
||||||
|
// Present only while a globe is attached, as when every engine
|
||||||
|
// built its own application: scripts read `engine.kind` from it.
|
||||||
|
window.__godsEyeView = debug;
|
||||||
},
|
},
|
||||||
detachScene() {
|
detachScene() {
|
||||||
|
if (window.__godsEyeView === debug) delete window.__godsEyeView;
|
||||||
Object.assign(debug, {
|
Object.assign(debug, {
|
||||||
viewer: null,
|
viewer: null,
|
||||||
engine: null,
|
engine: null,
|
||||||
|
|||||||
+9
-4
@@ -91,10 +91,12 @@ function createApplication(kind, engineSwitch) {
|
|||||||
return application;
|
return application;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The running application while a globe is attached to it; null while the
|
||||||
|
// page has no engine (between engines, or after a failed recovery).
|
||||||
function liveComponents() {
|
function liveComponents() {
|
||||||
return application?.getState().status === 'ready'
|
if (application?.getState().status !== 'ready') return null;
|
||||||
? application.getComponents()
|
const components = application.getComponents();
|
||||||
: null;
|
return components.scene?.engine ? components : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The page's application is created once; a switch detaches and attaches
|
// The page's application is created once; a switch detaches and attaches
|
||||||
@@ -108,7 +110,7 @@ const legacySwitch =
|
|||||||
// parked for a later switch back.
|
// parked for a later switch back.
|
||||||
async function leave(from) {
|
async function leave(from) {
|
||||||
const components = liveComponents();
|
const components = liveComponents();
|
||||||
const attached = Boolean(components?.scene.engine);
|
const attached = Boolean(components);
|
||||||
if (attached) {
|
if (attached) {
|
||||||
cover ??= keepGlobeFrame(components.scene.engine);
|
cover ??= keepGlobeFrame(components.scene.engine);
|
||||||
cockpit = components.controls.styleManager.getCockpitState();
|
cockpit = components.controls.styleManager.getCockpitState();
|
||||||
@@ -206,6 +208,9 @@ async function settle() {
|
|||||||
layerSession.handoffLayers = null;
|
layerSession.handoffLayers = null;
|
||||||
cockpit = null;
|
cockpit = null;
|
||||||
withdrawEngineRetry();
|
withdrawEngineRetry();
|
||||||
|
// An engine runs again after a failed recovery showed its error: the
|
||||||
|
// application, built once, does not start its loading chrome again.
|
||||||
|
loading.classList.add('hidden');
|
||||||
// The running engine owns the address again, including an entry visited
|
// The running engine owns the address again, including an entry visited
|
||||||
// before its share link listened or while it was held.
|
// before its share link listened or while it was held.
|
||||||
components.controls.styleManager.shareLinkManager?.resumeNavigation?.();
|
components.controls.styleManager.shareLinkManager?.resumeNavigation?.();
|
||||||
|
|||||||
Reference in new issue
Block a user