Wait for the traffic state the layer reaches in an oblique view

Roads the view cannot sample may keep their pending surface heights, as
before the refactoring; the oblique wait now asks for current coverage,
drawn traffic, finished loading and no scheduled refinement, and prints
every loading field when it times out.
This commit is contained in:
German Khokhlov committed 2026-11-11 02:27:40 +04:00
1 parent ed68be0610
commit af0dc2e2ec
2 files changed
+83 -6

No files matched your search

+10 -2
View File
@@ -49,7 +49,9 @@ export function trafficReadiness() {
pose.heading, pose.heading,
pose.pitch, pose.pitch,
]; ];
const stats = dataManager.layers.get('traffic').module.getStats(); const layer = dataManager.layers.get('traffic').module;
const stats = layer.getStats();
const work = layer.getLoadingDiagnostics();
const coverageViewKey = stats.roadBounds?.coverage?.viewKey ?? null; const coverageViewKey = stats.roadBounds?.coverage?.viewKey ?? null;
const coverage = coverageViewKey?.split(',').map(Number); const coverage = coverageViewKey?.split(',').map(Number);
// Allow rounding and sub-metre settling, not another altitude or camera view. // Allow rounding and sub-metre settling, not another altitude or camera view.
@@ -71,11 +73,17 @@ export function trafficReadiness() {
return { return {
viewKey: view.map((value) => Number(value).toFixed(7)).join(','), viewKey: view.map((value) => Number(value).toFixed(7)).join(','),
coverageViewKey, coverageViewKey,
viewMatches,
count: stats.count, count: stats.count,
loading: stats.loading, loading: stats.loading,
surfacePending: stats.surfacePending, surfacePending: stats.surfacePending,
...work,
error: stats.error,
detailError: stats.detailError,
// Missing local heights can outlive the last pass in an oblique view.
// Wait for acquisition, publication and scheduled refinement to finish.
settled: settled:
viewMatches && stats.count > 0 && !stats.loading && !stats.surfacePending, viewMatches && stats.count > 0 && !stats.loading && !work.surfaceWaiting,
}; };
} }
+73 -4
View File
@@ -67,8 +67,19 @@ test('traffic readiness rejects old coverage, pending debounce and partial popul
surfacePending: 0, surfacePending: 0,
roadBounds: null, roadBounds: null,
}; };
const work = { surfaceWaiting: false };
app.dataManager = { app.dataManager = {
layers: new Map([['traffic', { module: { getStats: () => stats } }]]), layers: new Map([
[
'traffic',
{
module: {
getStats: () => stats,
getLoadingDiagnostics: () => work,
},
},
],
]),
}; };
let calls = 0; let calls = 0;
let disposed = 0; let disposed = 0;
@@ -93,7 +104,17 @@ test('traffic readiness rejects old coverage, pending debounce and partial popul
assert.equal(predicate(), false, 'partial road population'); assert.equal(predicate(), false, 'partial road population');
stats.loading = false; stats.loading = false;
stats.surfacePending = 1; stats.surfacePending = 1;
assert.equal(predicate(), false, 'unresolved surface'); work.surfaceWaiting = true;
assert.equal(predicate(), false, 'scheduled surface work');
work.surfaceWaiting = false;
assert.equal(
predicate(),
true,
'unavailable heights with no more work',
);
stats.loading = true;
assert.equal(predicate(), false, 'active refinement still blocks');
stats.loading = false;
stats.surfacePending = 0; stats.surfacePending = 0;
assert.equal(predicate(), true); assert.equal(predicate(), true);
} }
@@ -123,7 +144,17 @@ test('traffic readiness accepts the layer footprint after rounding and tiny pose
roadBounds: createReticleFootprint()(app.engine, 300), roadBounds: createReticleFootprint()(app.engine, 300),
}; };
app.dataManager = { app.dataManager = {
layers: new Map([['traffic', { module: { getStats: () => stats } }]]), layers: new Map([
[
'traffic',
{
module: {
getStats: () => stats,
getLoadingDiagnostics: () => ({ surfaceWaiting: false }),
},
},
],
]),
}; };
assert.equal(trafficReadiness().settled, true); assert.equal(trafficReadiness().settled, true);
app.engine.fake.setPose({ app.engine.fake.setPose({
@@ -147,7 +178,17 @@ test('traffic readiness rejects changed views and malformed coverage keys', (t)
roadBounds: { coverage: {} }, roadBounds: { coverage: {} },
}; };
app.dataManager = { app.dataManager = {
layers: new Map([['traffic', { module: { getStats: () => stats } }]]), layers: new Map([
[
'traffic',
{
module: {
getStats: () => stats,
getLoadingDiagnostics: () => ({ surfaceWaiting: false }),
},
},
],
]),
}; };
for (const key of [ for (const key of [
null, null,
@@ -179,8 +220,23 @@ test('traffic loading timeouts include both keys and pending work', async (t) =>
count: 12, count: 12,
loading: true, loading: true,
surfacePending: 2, surfacePending: 2,
error: null,
detailError: null,
roadBounds: { coverage: { viewKey: coverageViewKey } }, roadBounds: { coverage: { viewKey: coverageViewKey } },
}), }),
getLoadingDiagnostics: () => ({
fetching: false,
flowPending: 0,
surfaceRefining: true,
surfaceWaiting: false,
populationPending: false,
heightPending: true,
generation: 3,
surfaceRevision: 7,
lastPaintStage: 'Loaded full refined',
movingWaypoints: 2,
maxResidualM: 12,
}),
}, },
}, },
], ],
@@ -209,6 +265,19 @@ test('traffic loading timeouts include both keys and pending work', async (t) =>
), ),
); );
assert.match(error.message, /"loading":true,"surfacePending":2/); assert.match(error.message, /"loading":true,"surfacePending":2/);
const observed = JSON.parse(error.message.split('; ')[1]);
assert.equal(observed.viewMatches, false);
assert.equal(observed.fetching, false);
assert.equal(observed.flowPending, 0);
assert.equal(observed.surfaceRefining, true);
assert.equal(observed.surfaceWaiting, false);
assert.equal(observed.populationPending, false);
assert.equal(observed.heightPending, true);
assert.equal(observed.generation, 3);
assert.equal(observed.surfaceRevision, 7);
assert.equal(observed.lastPaintStage, 'Loaded full refined');
assert.equal(observed.movingWaypoints, 2);
assert.equal(observed.maxResidualM, 12);
return true; return true;
}, },
); );