Share the surface sample budget between placements in one task

Two triggers firing together (tiles loaded and a camera move end) ran two
passes in the same task, each within 16 samples but 17 or more together.
Placements in one task now share the budget; a placement that finds it
spent continues in the next task, and that continuation starts a fresh
budget.
This commit is contained in:
German Khokhlov committed 2026-11-11 00:40:45 +04:00
1 parent 982d197111
commit 9e2c424030
2 files changed
+36 -2

No files matched your search

+20 -2
View File
@@ -135,6 +135,9 @@ export function createSurfacePoints(
let revision = 0;
let timer = null;
let destroyed = false;
// Placements in one task (two triggers at once) share one sample budget.
let taskSamples = 0;
let taskCounted = false;
function onScreen(position, size) {
if (screenMargin === Infinity) return true;
@@ -160,6 +163,12 @@ export function createSurfacePoints(
return plausible(height) ? height : Number.NaN;
}
// A pass continued by its own timer starts a new task's budget.
function continuePlacing() {
taskSamples = 0;
place();
}
// Points off screen are passed over until the next move or tile load.
function place({ all = false } = {}) {
if (all) revision++;
@@ -169,7 +178,14 @@ export function createSurfacePoints(
const size = engine.host.size();
const tiles = engine.maps?.photorealActive?.() === true;
const started = performance.now();
let samples = 0;
if (!taskCounted) {
taskCounted = true;
queueMicrotask(() => {
taskSamples = 0;
taskCounted = false;
});
}
let samples = taskSamples;
let visited = 0;
let moved = false;
let cameraGround;
@@ -179,7 +195,7 @@ export function createSurfacePoints(
const yieldPass = () => {
if (changed) set.commit();
if (changed || moved) request();
timer = setTimeout(place, 0);
timer = setTimeout(continuePlacing, 0);
};
for (let index = 0; index < points.length; index++) {
const point = points[index];
@@ -207,6 +223,7 @@ export function createSurfacePoints(
if (estimate && tiles && !plausible(ground)) {
if (cameraGround === undefined) {
samples++;
taskSamples = samples;
cameraGround = groundUnderCamera(exclude);
}
ground = cameraGround;
@@ -230,6 +247,7 @@ export function createSurfacePoints(
return;
}
samples++;
taskSamples = samples;
const height = readSurface(engine, point.cartographic, {
exclude,
prefer: typeof prefer === 'function' ? prefer(point) : prefer,
+16
View File
@@ -293,3 +293,19 @@ test('no tile sample starts once the pass has used its time', async (t) => {
`samples started at ${starts.join(', ')} ms`,
);
});
test('two placements in one task share one pass budget', async (t) => {
const { engine, surface } = setup(t, 40);
await engine.maps.setPhotorealTiles({});
engine.fake.script('terrainHeight', 10);
engine.fake.script('sampleHeight', 50);
const sampled = t.mock.method(engine.surface, 'sampleHeight');
surface.place();
surface.place({ all: true });
assert.ok(
sampled.mock.callCount() <= 16,
`${sampled.mock.callCount()} samples in one task`,
);
await new Promise((resolve) => setTimeout(resolve, 20));
assert.ok(sampled.mock.callCount() > 16, 'the deferred placement ran');
});