mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
Measure session object identity and listener counts across engine switches
window.__gevSession names each session object by a stable instance id and counts applications, attachments and detaches. scripts/qa-app-once.mjs (report-only with --report for now) runs three round trips and a parked-renderer rollback on a keyless preview with paid map, model and voice endpoints blocked.
This commit is contained in:
1 parent
5564156c09
commit
b2cc00833d
5 files changed
+716
No files matched your search
@@ -0,0 +1,66 @@
|
||||
// Identity and attachment counters for the page session, read by the switch
|
||||
// acceptance scripts (scripts/qa-app-once.mjs, qa-voice-engine-switch.mjs):
|
||||
// an engine switch keeps every session object and only re-attaches the parts
|
||||
// bound to an engine, so each id below stays the same across switches.
|
||||
|
||||
const ids = new WeakMap();
|
||||
let nextId = 0;
|
||||
|
||||
/**
|
||||
* A small number naming one object for this page: the same object always
|
||||
* reads the same id, a different object a different one. Null for values
|
||||
* that are not objects.
|
||||
* @param {unknown} value
|
||||
* @returns {number|null}
|
||||
*/
|
||||
export function instanceId(value) {
|
||||
if (
|
||||
value === null ||
|
||||
(typeof value !== 'object' && typeof value !== 'function')
|
||||
)
|
||||
return null;
|
||||
let id = ids.get(value);
|
||||
if (id === undefined) {
|
||||
id = ++nextId;
|
||||
ids.set(value, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The page's diagnostic handle (`window.__gevSession`).
|
||||
* `read()` returns the session objects by name; `ids` maps each name to its
|
||||
* {@link instanceId}. `count(name)` bumps a counter such as `attachments`.
|
||||
* @param {() => Record<string, unknown>} read
|
||||
*/
|
||||
export function createSessionDiagnostics(read) {
|
||||
const counters = {
|
||||
applications: 0,
|
||||
attachments: 0,
|
||||
detaches: 0,
|
||||
rollbacks: 0,
|
||||
};
|
||||
return Object.freeze({
|
||||
get ids() {
|
||||
return Object.fromEntries(
|
||||
Object.entries(read() ?? {}).map(([name, value]) => [
|
||||
name,
|
||||
instanceId(value),
|
||||
]),
|
||||
);
|
||||
},
|
||||
get counters() {
|
||||
return { ...counters };
|
||||
},
|
||||
get attachments() {
|
||||
return counters.attachments;
|
||||
},
|
||||
get detaches() {
|
||||
return counters.detaches;
|
||||
},
|
||||
count(name) {
|
||||
counters[name] = (counters[name] ?? 0) + 1;
|
||||
return counters[name];
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createSessionDiagnostics, instanceId } from './sessionDiagnostics.js';
|
||||
|
||||
test('an object keeps its id and another object gets another one', () => {
|
||||
const a = {};
|
||||
const b = {};
|
||||
const fn = () => {};
|
||||
assert.equal(instanceId(a), instanceId(a));
|
||||
assert.notEqual(instanceId(a), instanceId(b));
|
||||
assert.equal(typeof instanceId(fn), 'number');
|
||||
assert.equal(instanceId(null), null);
|
||||
assert.equal(instanceId(undefined), null);
|
||||
assert.equal(instanceId(3), null);
|
||||
});
|
||||
|
||||
test('the diagnostics read ids at the time they are asked for', () => {
|
||||
let shell = {};
|
||||
const voice = {};
|
||||
const diagnostics = createSessionDiagnostics(() => ({ shell, voice }));
|
||||
const first = diagnostics.ids;
|
||||
assert.equal(first.shell, instanceId(shell));
|
||||
assert.deepEqual(diagnostics.ids, first);
|
||||
shell = {};
|
||||
assert.notEqual(diagnostics.ids.shell, first.shell);
|
||||
assert.equal(diagnostics.ids.voice, first.voice);
|
||||
});
|
||||
|
||||
test('counters start at zero and count each event', () => {
|
||||
const diagnostics = createSessionDiagnostics(() => ({}));
|
||||
assert.equal(diagnostics.attachments, 0);
|
||||
diagnostics.count('attachments');
|
||||
diagnostics.count('attachments');
|
||||
diagnostics.count('detaches');
|
||||
assert.equal(diagnostics.attachments, 2);
|
||||
assert.equal(diagnostics.detaches, 1);
|
||||
assert.deepEqual(diagnostics.counters, {
|
||||
applications: 0,
|
||||
attachments: 2,
|
||||
detaches: 1,
|
||||
rollbacks: 0,
|
||||
});
|
||||
});
|
||||
+24
@@ -12,6 +12,7 @@ import {
|
||||
waitForFirstFrame,
|
||||
} from './standalone/engineSwitch.js';
|
||||
import { ENGINE_STORAGE_KEY } from './app/engineChoice.js';
|
||||
import { createSessionDiagnostics } from './app/sessionDiagnostics.js';
|
||||
import { useDisplayStore } from './ui/stores/display.js';
|
||||
import { applicationPinia } from './ui/vueRuntime.js';
|
||||
import { t } from './i18n/index.js';
|
||||
@@ -31,6 +32,25 @@ let cockpit = null;
|
||||
const renderers = createRendererCache();
|
||||
const layerSession = { lifetime: new AbortController() };
|
||||
const { signal } = layerSession.lifetime;
|
||||
// Which session objects this page holds now (scripts/qa-app-once.mjs).
|
||||
const session = createSessionDiagnostics(() => {
|
||||
const components = application?.getComponents?.() ?? {};
|
||||
const style = components.controls?.styleManager;
|
||||
return {
|
||||
application,
|
||||
shell: style,
|
||||
shareLink: style?.shareLinkManager,
|
||||
hud: style?.hud,
|
||||
navigation: style?._navigation,
|
||||
placeSearch: style?.placeSearch,
|
||||
dataManager: components.data?.dataManager,
|
||||
voice: components.tools?.voiceCommands,
|
||||
annotations: components.tools?.annotations,
|
||||
geoImport: window.__gevGeoImport,
|
||||
drawTool: window.__gevDrawTool,
|
||||
};
|
||||
});
|
||||
window.__gevSession = session;
|
||||
window.addEventListener('pagehide', (event) => {
|
||||
if (event.persisted) return;
|
||||
layerSession.lifetime.abort();
|
||||
@@ -56,6 +76,7 @@ window.addEventListener('popstate', () => {
|
||||
});
|
||||
|
||||
function createApplication(kind, engineSwitch) {
|
||||
session.count('applications');
|
||||
layerSession.restoringView = engineSwitch;
|
||||
application = createStandaloneApplication({
|
||||
googleApiKey: import.meta.env.GOOGLE_MAPS_API_KEY,
|
||||
@@ -91,6 +112,7 @@ async function leave(from) {
|
||||
renderers.retain(from);
|
||||
}
|
||||
await application?.destroy();
|
||||
session.count('detaches');
|
||||
}
|
||||
|
||||
// Start an engine on the retained session. Success means the engine runs
|
||||
@@ -105,6 +127,7 @@ async function enter(kind) {
|
||||
await style.initialRestorePromise;
|
||||
signal.throwIfAborted();
|
||||
layerSession.restoringView = false;
|
||||
session.count('attachments');
|
||||
resumeSession(layerSession);
|
||||
// Only resume the aircraft that actually survived the handoff. A history
|
||||
// entry may select a different subject, and a stale track may disappear.
|
||||
@@ -215,6 +238,7 @@ const initialKind = pageEngineKind();
|
||||
createApplication(initialKind, false)
|
||||
.start()
|
||||
.then(() => {
|
||||
session.count('attachments');
|
||||
engineSwitch = createEngineSwitch({
|
||||
kind: initialKind,
|
||||
requested: pageEngineKind,
|
||||
|
||||
Reference in new issue
Block a user