Settle pending Traffic height work when the population empties

A closure that removes the last animated points mid height correction
released the render hold, and animate() then returned before easeHeights()
could run, leaving _heightPending (and getStats().loading) true. Settle
every retained road on its corrected heights before the hold is released.

Adds a regression combining height correction, a late closure and an
empty population.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-23 12:12:27 +01:00
1 parent d8dce93ff4
commit 090eeb9b11
4 files changed
+59 -1

No files matched your search

+3
View File
@@ -25,6 +25,9 @@ export function createAnimation({
layerState._enabled &&
(layerState._dots.length || layerState._heatJamPrim),
);
// Height easing only runs in a held frame. Settle it before releasing, so
// a population emptied mid-correction cannot report loading forever.
if (!needed && layerState._heightPending) parts.retention.finishHeights();
if (needed === Boolean(layerState._renderHeld)) return;
layerState._renderHeld = needed;
if (needed) {
+31
View File
@@ -565,6 +565,37 @@ test('provisional opposite-direction heights cannot overwrite a measured sibling
assert.notEqual(forward.waypoints, reverse.waypoints);
});
test('a closure that empties the population mid-correction settles heights and clears loading before release', (t) => {
const h = setup(t);
h.parts.retention.reconcile(h.roads(), 450, true);
h.settle();
assert.equal(h.state._renderHeld, true);
const road = h.state._roads[0];
// Republishing the sole road starts a height correction.
const corrected = h.roads(0, 120);
h.parts.retention.reconcile(corrected, 450, true);
assert.equal(h.state._heightPending, true);
assert.equal(road.heightMoving, true);
// The closure arrives before the next population pass, which then removes
// the final points after their fade, long before the correction finishes.
corrected[0].flow = { level: 0, closure: true };
for (let i = 0; i < 20 && h.state._dots.length; i++) h.frame();
assert.equal(h.state._dots.length, 0);
assert.equal(h.state._heatJamPrim, null);
assert.equal(h.state._renderHeld, false);
assert.equal(h.state._heightPending, false);
assert.equal(h.state._populationPending, false);
assert.equal(road.heightMoving, false);
assert.ok(
Math.abs(C.Cartographic.fromCartesian(road.waypoints[0]).height - 120) <
0.01,
);
// Frames driven by other layers stay idle and cannot revive the work.
h.frame();
assert.equal(h.state._heightPending, false);
assert.equal(h.state._renderHeld, false);
});
test('large height corrections obey a screen-pixel bound and reuse projection scratch objects', (t) => {
const h = setup(t);
h.state._viewer.scene = {
+24 -1
View File
@@ -242,6 +242,29 @@ export function createRetention({ state, parts }) {
road.heightMoving = moving;
}
}
/**
* Settle every retained road on its corrected heights at once. Easing only
* runs inside the held animation frame, so once nothing is left to animate
* the remaining correction would otherwise stay pending indefinitely.
* Published roads whose record was just dropped are settled too, so a later
* held frame cannot revive their correction.
*/
function finishHeights() {
const roads = new Set(state._roads);
for (const record of records.values()) roads.add(record.road);
for (const road of roads) {
if (!road.heightMoving) continue;
for (let i = 0; i < road.waypoints.length; i++)
Cesium.Cartesian3.clone(road.targetWaypoints[i], road.waypoints[i]);
for (let i = 0; i < road.segmentDist.length; i++)
road.segmentDist[i] = Cesium.Cartesian3.distance(
road.waypoints[i],
road.waypoints[i + 1],
);
road.heightMoving = false;
}
state._heightPending = false;
}
function clear() {
if (timer !== null) {
if (typeof cancelAnimationFrame === 'function')
@@ -253,5 +276,5 @@ export function createRetention({ state, parts }) {
state._populationPending = false;
state._heightPending = false;
}
return { reconcile, easeHeights, clear, refreshCounts };
return { reconcile, easeHeights, finishHeights, clear, refreshCounts };
}