perf(map): stage cesium provider startup
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
runStagedMapProviders,
|
||||
waitForGlobeViewportReady,
|
||||
} from "../apps/catalog/src/mapProviderStartup.mjs";
|
||||
|
||||
function eventHarness() {
|
||||
const listeners = new Set();
|
||||
return {
|
||||
event: {
|
||||
addEventListener(listener) {
|
||||
listeners.add(listener);
|
||||
return () => listeners.delete(listener);
|
||||
},
|
||||
},
|
||||
raise(...args) {
|
||||
for (const listener of [...listeners]) listener(...args);
|
||||
},
|
||||
get size() {
|
||||
return listeners.size;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test("providers start in base-map order", async () => {
|
||||
const calls = [];
|
||||
const result = await runStagedMapProviders({
|
||||
loadImagery: () => calls.push("imagery"),
|
||||
waitAfterImagery: () => calls.push("imagery-ready"),
|
||||
loadTerrain: () => calls.push("terrain"),
|
||||
waitAfterTerrain: () => calls.push("terrain-ready"),
|
||||
loadBuildings: () => calls.push("buildings"),
|
||||
loadDeferred: () => calls.push("deferred"),
|
||||
});
|
||||
|
||||
assert.equal(result, "complete");
|
||||
assert.deepEqual(calls, ["imagery", "imagery-ready", "terrain", "terrain-ready", "buildings", "deferred"]);
|
||||
});
|
||||
|
||||
test("imagery failure is reported without blocking terrain or buildings", async () => {
|
||||
const calls = [];
|
||||
const errors = [];
|
||||
await runStagedMapProviders({
|
||||
loadImagery: () => { calls.push("imagery"); throw new Error("imagery failed"); },
|
||||
waitAfterImagery: () => calls.push("imagery-ready"),
|
||||
loadTerrain: () => calls.push("terrain"),
|
||||
waitAfterTerrain: () => calls.push("terrain-ready"),
|
||||
loadBuildings: () => calls.push("buildings"),
|
||||
onProviderError: (provider, error) => errors.push([provider, error.message]),
|
||||
});
|
||||
|
||||
assert.deepEqual(calls, ["imagery", "terrain", "terrain-ready", "buildings"]);
|
||||
assert.deepEqual(errors, [["imagery", "imagery failed"]]);
|
||||
});
|
||||
|
||||
test("terrain failure is reported without blocking buildings", async () => {
|
||||
const calls = [];
|
||||
const errors = [];
|
||||
await runStagedMapProviders({
|
||||
loadImagery: () => calls.push("imagery"),
|
||||
waitAfterImagery: () => calls.push("imagery-ready"),
|
||||
loadTerrain: () => { calls.push("terrain"); throw new Error("terrain failed"); },
|
||||
waitAfterTerrain: () => calls.push("terrain-ready"),
|
||||
loadBuildings: () => calls.push("buildings"),
|
||||
onProviderError: (provider, error) => errors.push([provider, error.message]),
|
||||
});
|
||||
|
||||
assert.deepEqual(calls, ["imagery", "imagery-ready", "terrain", "buildings"]);
|
||||
assert.deepEqual(errors, [["terrain", "terrain failed"]]);
|
||||
});
|
||||
|
||||
test("cancellation prevents all later stages", async () => {
|
||||
const controller = new AbortController();
|
||||
const calls = [];
|
||||
const result = await runStagedMapProviders({
|
||||
signal: controller.signal,
|
||||
loadImagery: () => { calls.push("imagery"); controller.abort(); },
|
||||
waitAfterImagery: () => calls.push("imagery-ready"),
|
||||
loadTerrain: () => calls.push("terrain"),
|
||||
loadBuildings: () => calls.push("buildings"),
|
||||
});
|
||||
|
||||
assert.equal(result, "cancelled");
|
||||
assert.deepEqual(calls, ["imagery"]);
|
||||
});
|
||||
|
||||
test("a stalled provider times out without inserting late or blocking later stages", async () => {
|
||||
const calls = [];
|
||||
const errors = [];
|
||||
let releaseImagery;
|
||||
const stalledImagery = new Promise((resolve) => { releaseImagery = resolve; });
|
||||
await runStagedMapProviders({
|
||||
providerInitializationTimeoutMs: 1,
|
||||
loadImagery: async ({ signal }) => {
|
||||
calls.push("imagery-start");
|
||||
await stalledImagery;
|
||||
if (!signal.aborted) calls.push("imagery-insert");
|
||||
},
|
||||
loadTerrain: () => calls.push("terrain"),
|
||||
loadBuildings: () => calls.push("buildings"),
|
||||
onProviderError: (provider, error) => errors.push([provider, error.message]),
|
||||
});
|
||||
releaseImagery();
|
||||
await Promise.resolve();
|
||||
|
||||
assert.deepEqual(calls, ["imagery-start", "terrain", "buildings"]);
|
||||
assert.deepEqual(errors, [["imagery", "imagery_startup_timeout"]]);
|
||||
});
|
||||
|
||||
test("viewport readiness waits for discovery frames and cleans listeners", async () => {
|
||||
const progress = eventHarness();
|
||||
const postRender = eventHarness();
|
||||
const globe = { tilesLoaded: true, tileLoadProgressEvent: progress.event };
|
||||
const scene = { postRender: postRender.event, requestRender() {} };
|
||||
let settled = false;
|
||||
const ready = waitForGlobeViewportReady({ globe, scene, timeoutMs: 100, minimumRenderFrames: 2 })
|
||||
.then((reason) => { settled = true; return reason; });
|
||||
|
||||
postRender.raise();
|
||||
await Promise.resolve();
|
||||
assert.equal(settled, false);
|
||||
postRender.raise();
|
||||
assert.equal(await ready, "loaded");
|
||||
assert.equal(progress.size, 0);
|
||||
assert.equal(postRender.size, 0);
|
||||
});
|
||||
|
||||
test("viewport readiness times out and aborts without leaking listeners", async () => {
|
||||
const timeoutProgress = eventHarness();
|
||||
const timeoutPostRender = eventHarness();
|
||||
assert.equal(await waitForGlobeViewportReady({
|
||||
globe: { tilesLoaded: false, tileLoadProgressEvent: timeoutProgress.event },
|
||||
scene: { postRender: timeoutPostRender.event, requestRender() {} },
|
||||
timeoutMs: 1,
|
||||
}), "timeout");
|
||||
assert.equal(timeoutProgress.size, 0);
|
||||
assert.equal(timeoutPostRender.size, 0);
|
||||
|
||||
const abortProgress = eventHarness();
|
||||
const abortPostRender = eventHarness();
|
||||
const controller = new AbortController();
|
||||
const pending = waitForGlobeViewportReady({
|
||||
globe: { tilesLoaded: false, tileLoadProgressEvent: abortProgress.event },
|
||||
scene: { postRender: abortPostRender.event, requestRender() {} },
|
||||
signal: controller.signal,
|
||||
timeoutMs: 100,
|
||||
});
|
||||
controller.abort();
|
||||
assert.equal(await pending, "cancelled");
|
||||
assert.equal(abortProgress.size, 0);
|
||||
assert.equal(abortPostRender.size, 0);
|
||||
});
|
||||
|
||||
test("the live renderer uses the staged provider coordinator", async () => {
|
||||
const renderer = await readFile(new URL("../apps/catalog/src/CesiumMapRenderer.tsx", import.meta.url), "utf8");
|
||||
assert.match(renderer, /runStagedMapProviders\(\{/);
|
||||
assert.match(renderer, /waitForGlobeViewportReady\(\{/);
|
||||
assert.match(renderer, /providerStartupAbort\.abort\(\)/);
|
||||
assert.doesNotMatch(renderer, /Do not serialize provider startup/);
|
||||
});
|
||||
Reference in New Issue
Block a user