Share the traffic refinement deadline across passes

Each refinement pass renewed the ten-second deadline, so changing
surface readiness could keep the layer refining, and loading, without
end. The passes now share one deadline, and the scheduled surface work is
reported as surfaceWaiting.
This commit is contained in:
German Khokhlov committed 2026-11-11 02:27:40 +04:00
1 parent b870f446c6
commit ed68be0610
3 files changed
+50 -2

No files matched your search

+1
View File
@@ -356,6 +356,7 @@ export function createControls({ state: layerState, services, parts, source }) {
fetching: Boolean(layerState._fetching), fetching: Boolean(layerState._fetching),
flowPending: layerState._flowPending, flowPending: layerState._flowPending,
surfaceRefining: Boolean(layerState._surfaceRefining), surfaceRefining: Boolean(layerState._surfaceRefining),
surfaceWaiting: Boolean(layerState._surfaceRefineRemove),
populationPending: Boolean(layerState._populationPending), populationPending: Boolean(layerState._populationPending),
heightPending: Boolean(layerState._heightPending), heightPending: Boolean(layerState._heightPending),
generation: layerState._loadGeneration, generation: layerState._loadGeneration,
+9 -2
View File
@@ -277,6 +277,7 @@ export function createFlow({ state: layerState, services, parts, source }) {
layerState._surfaceRefining = false; layerState._surfaceRefining = false;
layerState._surfaceRefineRemove?.(); layerState._surfaceRefineRemove?.();
layerState._surfaceRefineRemove = null; layerState._surfaceRefineRemove = null;
layerState._surfaceRefineDeadline = null;
} }
if (!roads.length) { if (!roads.length) {
// A streamed tile with no roads changes nothing on screen. A completed // A streamed tile with no roads changes nothing on screen. A completed
@@ -557,6 +558,7 @@ export function createFlow({ state: layerState, services, parts, source }) {
// without leaving offscreen or unavailable stations loading forever. // without leaving offscreen or unavailable stations loading forever.
const retryPending = const retryPending =
prepared.pending.length > 0 && prepared.pending.length > 0 &&
(!refining || performance.now() < layerState._surfaceRefineDeadline) &&
(!refining || (!refining ||
!trafficSurfaceReady(engine) || !trafficSurfaceReady(engine) ||
prepared.metrics.resolvedSamples > 0); prepared.metrics.resolvedSamples > 0);
@@ -570,6 +572,8 @@ export function createFlow({ state: layerState, services, parts, source }) {
(!refining && ready.some((road) => road.surfaceSettled === false))) (!refining && ready.some((road) => road.surfaceSettled === false)))
) { ) {
layerState._surfaceRefineRemove?.(); layerState._surfaceRefineRemove?.();
// A ready frame can start another pass; it must not renew the budget.
layerState._surfaceRefineDeadline ??= performance.now() + 10000;
const stop = () => { const stop = () => {
remove(); remove();
clearTimeout(timer); clearTimeout(timer);
@@ -609,10 +613,13 @@ export function createFlow({ state: layerState, services, parts, source }) {
layerState._surfaceRefining = false; layerState._surfaceRefining = false;
}); });
}); });
const timer = setTimeout(() => { const timer = setTimeout(
() => {
stop(); stop();
if (current()) layerState._surfacePending = 0; if (current()) layerState._surfacePending = 0;
}, 10000); },
Math.max(0, layerState._surfaceRefineDeadline - performance.now()),
);
layerState._surfaceRefineRemove = stop; layerState._surfaceRefineRemove = stop;
engine.render.request('traffic-surface'); engine.render.request('traffic-surface');
} }
+40
View File
@@ -306,6 +306,7 @@ test('a completed surface defers unavailable roads until the next view instead o
}); });
test('the surface wait deadline defers pending roads when the mesh never settles', async (t) => { test('the surface wait deadline defers pending roads when the mesh never settles', async (t) => {
t.mock.method(performance, 'now', () => 0);
const surface = incomingGlobe(t); const surface = incomingGlobe(t);
surface.frame(); surface.frame();
const { state, flow } = fixture(surface); const { state, flow } = fixture(surface);
@@ -336,6 +337,45 @@ test('the surface wait deadline defers pending roads when the mesh never settles
assert.equal(state._surfaceRefineRemove, null); assert.equal(state._surfaceRefineRemove, null);
}); });
test('successive surface-ready frames cannot restart the refinement deadline', async (t) => {
const surface = incomingGlobe(t);
surface.ready = true;
surface.frame();
const { state, flow } = fixture(surface);
t.after(() => {
state._activeFetchAbort.abort();
state._surfaceRefineRemove?.();
});
let now = 0;
t.mock.method(performance, 'now', () => now);
surface.engine.fake.script('terrainHeight', (point) => {
if (geo.math.toDegrees(point.latitude) <= LAT + 0.004) return GROUND;
// A ready frame can be followed by more tile work during a surface pass.
surface.ready = false;
surface.frame();
return undefined;
});
const roads = [road(0, GROUND), road(0.008, GROUND)];
await flow.applyFlowThenRender(
roads,
{ ...bounds, coverage },
1,
1800,
'Loaded full',
);
assert.equal(state._surfacePending, 1);
for (let pass = 0; pass < 6 && state._surfaceRefineRemove; pass++) {
now += 2000;
surface.ready = true;
surface.frame();
await settle(() => !state._surfaceRefining);
}
assert.equal(state._surfacePending, 0, 'the whole refinement has a deadline');
assert.equal(state._surfaceRefining, false);
assert.equal(state._surfaceRefineRemove, null);
assert.equal(roads[1].surfaceReady, false, 'missing heights stay hidden');
});
test('resuming the viewport republishes the retained roads as a pass that can still refine', () => { test('resuming the viewport republishes the retained roads as a pass that can still refine', () => {
const labels = []; const labels = [];
const state = createState({ services: {} }); const state = createState({ services: {} });