mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
fix(globe): Navara redraws after a map change while its tiles arrive
Phase-4 QA: after a map-stack switch at the whole-globe view Navara sometimes showed the new map only seconds later. Navara 0.2.1 reports idle and stops drawing before a raster tile it fetched arrives, so the last frame stays stale until something else asks for one. Every engine.maps change (base imagery, terrain, photoreal tiles, their release) now asks for a frame and keeps asking every 250 ms for 6 s, then the engine idles again. scripts/qa-map-switch.mjs switches the stack (photoreal / Esri / OSM) at 18 000 km on each engine, waits for the tiles, hashes the picture, forces frames and hashes again: a change means a stale picture. Before: Navara 3/8 switches drew the new picture by themselves; after: Navara 8/8, Cesium 8/8.
This commit is contained in:
1 parent
ba039f7974
commit
907cb2092d
3 files changed
+87
-8
No files matched your search
@@ -1108,5 +1108,6 @@
|
||||
"src/app/layers/qaSources.js",
|
||||
"src/app/layers/qaSources.test.mjs",
|
||||
"scripts/qa-w1-layers.mjs",
|
||||
"src/bench/w1Feeds.js"
|
||||
"src/bench/w1Feeds.js",
|
||||
"scripts/qa-map-switch.mjs"
|
||||
]
|
||||
@@ -57,15 +57,47 @@ function imageryFor(source) {
|
||||
}
|
||||
}
|
||||
|
||||
// After a map change Navara 0.2.1 can report idle and stop drawing before a
|
||||
// raster tile it fetched arrives (measured at the whole-globe view: the
|
||||
// stack switch drew a stale picture until something else asked for a
|
||||
// frame). For a while after each change the engine asks for frames itself.
|
||||
const SETTLE_MS = 6000;
|
||||
const SETTLE_EVERY_MS = 250;
|
||||
|
||||
/**
|
||||
* @param {object} view A Navara ThreeView (addSource, addLayer, attribution, forceUpdate).
|
||||
* @param {{ fetch?: typeof fetch }} [options]
|
||||
* @param {{ fetch?: typeof fetch, setInterval?: Function,
|
||||
* clearInterval?: Function, now?: () => number }} [options] timers: test seams.
|
||||
* @returns {{ maps: object, destroy: () => void }}
|
||||
*/
|
||||
export function createNavaraMaps(
|
||||
view,
|
||||
{ fetch: fetchJson = globalThis.fetch } = {},
|
||||
{
|
||||
fetch: fetchJson = globalThis.fetch,
|
||||
setInterval: every = globalThis.setInterval,
|
||||
clearInterval: stopEvery = globalThis.clearInterval,
|
||||
now = () => performance.now(),
|
||||
} = {},
|
||||
) {
|
||||
let settleTimer = null;
|
||||
let settleUntil = 0;
|
||||
const stopSettling = () => {
|
||||
if (settleTimer !== null) stopEvery(settleTimer);
|
||||
settleTimer = null;
|
||||
};
|
||||
// A frame now, and frames for SETTLE_MS while tiles arrive.
|
||||
const changed = () => {
|
||||
view.forceUpdate();
|
||||
settleUntil = now() + SETTLE_MS;
|
||||
if (settleTimer !== null || destroyed) return;
|
||||
settleTimer = every(() => {
|
||||
if (destroyed || now() >= settleUntil) {
|
||||
stopSettling();
|
||||
return;
|
||||
}
|
||||
view.forceUpdate();
|
||||
}, SETTLE_EVERY_MS);
|
||||
};
|
||||
const slots = { imagery: null, terrain: null, photoreal: null };
|
||||
// Base imagery and photoreal tiles share one switch order (display): the
|
||||
// call made last decides what shows.
|
||||
@@ -105,7 +137,7 @@ export function createNavaraMaps(
|
||||
credits: shown,
|
||||
hidden: false,
|
||||
};
|
||||
view.forceUpdate();
|
||||
changed();
|
||||
}
|
||||
|
||||
// Base imagery under photoreal is hidden rather than deleted (see show).
|
||||
@@ -120,7 +152,7 @@ export function createNavaraMaps(
|
||||
});
|
||||
if (slot.credits.length)
|
||||
view.attribution?.[shown ? 'add' : 'remove'](slot.credits);
|
||||
view.forceUpdate();
|
||||
changed();
|
||||
}
|
||||
|
||||
async function terrainFor(source) {
|
||||
@@ -182,7 +214,7 @@ export function createNavaraMaps(
|
||||
if (destroyed || generation !== generations.terrain) return;
|
||||
if (!description) {
|
||||
clear('terrain');
|
||||
view.forceUpdate();
|
||||
changed();
|
||||
return;
|
||||
}
|
||||
if (slots.terrain?.key === description.url) return;
|
||||
@@ -198,7 +230,7 @@ export function createNavaraMaps(
|
||||
setImageryShown(true);
|
||||
currentBase = baseSource;
|
||||
}
|
||||
view.forceUpdate();
|
||||
changed();
|
||||
return;
|
||||
}
|
||||
const key = String(source.googleApiKey || '').trim();
|
||||
@@ -235,7 +267,7 @@ export function createNavaraMaps(
|
||||
if (tilesSource !== source) return;
|
||||
tilesSource = null;
|
||||
clear('photoreal');
|
||||
view.forceUpdate();
|
||||
changed();
|
||||
},
|
||||
photorealActive: () => slots.photoreal != null,
|
||||
current: () => ({ baseImagery: currentBase, photoreal: tilesSource }),
|
||||
@@ -246,6 +278,7 @@ export function createNavaraMaps(
|
||||
destroy() {
|
||||
if (destroyed) return;
|
||||
destroyed = true;
|
||||
stopSettling();
|
||||
for (const name of Object.keys(slots)) clear(name);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -295,3 +295,48 @@ test('releasePhotorealTiles forgets the source and takes its tiles away if shown
|
||||
assert.equal(maps.current().photoreal, null);
|
||||
assert.equal(layersOf(view, '3d-tiles').length, 0);
|
||||
});
|
||||
|
||||
test('after a map change Navara keeps redrawing while tiles arrive, then stops', async () => {
|
||||
const view = fakeView();
|
||||
const timers = [];
|
||||
let now = 0;
|
||||
const { maps, destroy } = createNavaraMaps(view, {
|
||||
setInterval: (fn, ms) => {
|
||||
const timer = { fn, ms, cleared: false };
|
||||
timers.push(timer);
|
||||
return timer;
|
||||
},
|
||||
clearInterval: (timer) => {
|
||||
if (timer) timer.cleared = true;
|
||||
},
|
||||
now: () => now,
|
||||
});
|
||||
const tick = (ms) => {
|
||||
for (let t = 0; t < ms; t += 250) {
|
||||
now += 250;
|
||||
for (const timer of timers) if (!timer.cleared) timer.fn();
|
||||
}
|
||||
};
|
||||
await maps.setBaseImagery({ kind: 'osm' });
|
||||
const afterChange = view.frames;
|
||||
tick(1000);
|
||||
// Navara 0.2.1 can report idle and stop drawing before a raster tile it
|
||||
// fetched arrives: the engine asks for frames for a while after a change.
|
||||
assert.ok(view.frames >= afterChange + 4, `frames ${view.frames}`);
|
||||
tick(10_000);
|
||||
const settled = view.frames;
|
||||
tick(5_000);
|
||||
assert.equal(view.frames, settled, 'idle again once the window ends');
|
||||
assert.ok(
|
||||
timers.every((timer) => timer.cleared),
|
||||
'no timer left running',
|
||||
);
|
||||
// Another change opens a new window; destroy ends it.
|
||||
await maps.setBaseImagery({ kind: 'esri-imagery' });
|
||||
tick(500);
|
||||
assert.ok(view.frames > settled + 1);
|
||||
destroy();
|
||||
const atDestroy = view.frames;
|
||||
tick(2000);
|
||||
assert.equal(view.frames, atDestroy);
|
||||
});
|
||||
Reference in new issue
Block a user