Stop refining traffic surfaces that make no progress

An oblique view kept roads whose samples could not resolve from it, and
the refinement retried them without end, leaving the layer loading with
work pending. A refinement pass that resolves nothing new stops, and work
still pending at the existing deadline is dropped.
This commit is contained in:
German Khokhlov committed 2026-11-11 02:07:36 +04:00
1 parent ed6b6114cf
commit 00cb44ad2c
4 files changed
+120 -5

No files matched your search

+16 -4
View File
@@ -552,12 +552,21 @@ export function createFlow({ state: layerState, services, parts, source }) {
paint();
}
paint(true);
if (current()) layerState._surfacePending = prepared.pending.length;
// A completed-mesh pass with no new heights cannot improve this view.
// Keep the roads in _surfaceInput for a later view or surface change,
// without leaving offscreen or unavailable stations loading forever.
const retryPending =
prepared.pending.length > 0 &&
(!refining ||
!trafficSurfaceReady(engine) ||
prepared.metrics.resolvedSamples > 0);
if (current())
layerState._surfacePending = retryPending ? prepared.pending.length : 0;
if (
current() &&
!preview &&
((!refining && !trafficSurfaceReady(engine)) ||
prepared.pending.length ||
retryPending ||
(!refining && ready.some((road) => road.surfaceSettled === false)))
) {
layerState._surfaceRefineRemove?.();
@@ -579,7 +588,7 @@ export function createFlow({ state: layerState, services, parts, source }) {
stop();
// Revalidate only local samples taken at a coarser rendered LOD. This
// background upgrade never holds road acquisition or first paint;
// missing samples keep retrying while refined heights stay cached.
// missing samples retry while heights make progress and stay cached.
layerState._surfaceRefining = true;
applyFlowThenRender(
roads,
@@ -600,7 +609,10 @@ export function createFlow({ state: layerState, services, parts, source }) {
layerState._surfaceRefining = false;
});
});
const timer = setTimeout(stop, 10000);
const timer = setTimeout(() => {
stop();
if (current()) layerState._surfacePending = 0;
}, 10000);
layerState._surfaceRefineRemove = stop;
engine.render.request('traffic-surface');
}
+95
View File
@@ -241,6 +241,101 @@ test('a still camera drains pending surface samples across bounded passes using
assert.equal(engine.fake.renderRequests, finished, 'settled work is idle');
});
test('a completed surface defers unavailable roads until the next view instead of refining forever', async (t) => {
const surface = incomingGlobe(t);
surface.ready = true;
surface.frame();
const { engine } = surface;
const { state, flow } = fixture(surface);
t.after(() => {
state._activeFetchAbort.abort();
state._surfaceRefineRemove?.();
});
const roads = [road(0, GROUND), road(0.008, GROUND)];
let missing = true;
engine.fake.script('terrainHeight', (point) =>
missing && geo.math.toDegrees(point.latitude) > LAT + 0.004
? undefined
: GROUND,
);
await flow.applyFlowThenRender(
roads,
{ ...bounds, coverage },
1,
1800,
'Loaded full',
);
let rendered = engine.fake.renderRequests;
await settle(() => {
if (engine.fake.renderRequests > rendered || state._surfaceRefineRemove) {
rendered = engine.fake.renderRequests;
surface.frame();
}
return !state._surfacePending && !state._surfaceRefining;
});
assert.equal(
state._surfacePending,
0,
'unavailable roads are not outstanding work',
);
assert.equal(state._surfaceRefining, false);
assert.equal(state._surfaceRefineRemove, null);
assert.equal(roads[1].surfaceReady, false, 'no unmeasured road is displayed');
const finished = engine.fake.renderRequests;
surface.frame();
await nextTask();
assert.equal(
engine.fake.renderRequests,
finished,
'the completed view is idle',
);
missing = false;
await flow.applyFlowThenRender(
roads,
{ ...bounds, coverage },
1,
1800,
'Loaded full',
);
assert.equal(
roads[1].surfaceReady,
true,
'another view retries the retained road',
);
});
test('the surface wait deadline defers pending roads when the mesh never settles', async (t) => {
const surface = incomingGlobe(t);
surface.frame();
const { state, flow } = fixture(surface);
t.after(() => {
state._activeFetchAbort.abort();
state._surfaceRefineRemove?.();
});
const schedule = globalThis.setTimeout;
let deadline;
t.mock.method(globalThis, 'setTimeout', (callback, delay, ...args) => {
if (delay === 10000) deadline = callback;
return schedule(callback, delay, ...args);
});
surface.engine.fake.script('terrainHeight', (point) =>
geo.math.toDegrees(point.latitude) > LAT + 0.004 ? undefined : GROUND,
);
await flow.applyFlowThenRender(
[road(0, GROUND), road(0.008, GROUND)],
{ ...bounds, coverage },
1,
1800,
'Loaded full',
);
assert.equal(state._surfacePending, 1);
assert.equal(typeof deadline, 'function');
deadline();
assert.equal(state._surfacePending, 0);
assert.equal(state._surfaceRefineRemove, null);
});
test('resuming the viewport republishes the retained roads as a pass that can still refine', () => {
const labels = [];
const state = createState({ services: {} });
+2
View File
@@ -258,6 +258,7 @@ export async function prepareRoadSurfaces(
const metrics = {
sampleCount: 0,
sampleMs: 0,
resolvedSamples: 0,
cacheHits: 0,
depthHits: 0,
roads: roads.length,
@@ -341,6 +342,7 @@ export async function prepareRoadSurfaces(
height = validHeight(floor) ? floor : 0;
}
} else {
metrics.resolvedSamples++;
height = validHeight(floor) ? Math.max(sampled, floor) : sampled;
cache.heights.delete(key);
cache.heights.set(key, {
+7 -1
View File
@@ -89,6 +89,7 @@ test('per-vertex mesh heights follow terrain, reject invalid samples, and leave
onMetrics: (value) => (metrics = value),
});
assert.equal(metrics.sampleCount, 5);
assert.equal(metrics.resolvedSamples, 2);
assert.equal(prepared.pending.length, 1);
assert.equal(samples, 5);
const actual = r.waypoints.map((p) => Math.round(heightOf(p)));
@@ -108,7 +109,12 @@ test('per-vertex mesh heights follow terrain, reject invalid samples, and leave
picks++;
return 75;
});
await prepareRoadSurfaces([r], engine, ground, []);
const cached = await prepareRoadSurfaces([r], engine, ground, []);
assert.equal(
cached.metrics.resolvedSamples,
0,
'cache hits are not progress',
);
assert.equal(picks, 0, 'validated coordinate heights survive cache hits');
const abort = new AbortController();
abort.abort();