mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
perf(cables): show landing markers a few per frame
Switching Submarine cables on after the globe had loaded held the page for about 1.4 s in one task. 1.2 s of it was Cesium reading the terrain height for each of the 1,917 ground-clamped landing markers as it created them, all in the first frame, including the ones behind the globe that the horizon sweep hid a moment later. The markers now start hidden and the sweep shows the visible ones 48 per rendered frame. Co-Authored-By: Claude Fable 5.2 <[email protected]>
This commit is contained in:
1 parent
188f0ccd9b
commit
f42980a04d
7 files changed
+120
-9
No files matched your search
@@ -8158,6 +8158,12 @@ Cesium's multipart suffixes. Each segment gets its own feature's styling, click
|
|||||||
destination and horizon-culling reference, and each source feature has one
|
destination and horizon-culling reference, and each source feature has one
|
||||||
reference stem. The source collections are not modified.
|
reference stem. The source collections are not modified.
|
||||||
|
|
||||||
|
Landing markers are ground-clamped billboards, and Cesium reads the terrain
|
||||||
|
height as it creates each one. They start hidden and the horizon sweep shows
|
||||||
|
the visible ones 48 per rendered frame (`CABLE_LANDING_REVEALS_PER_FRAME`),
|
||||||
|
so switching the layer on does not do every read in one task; hiding is still
|
||||||
|
immediate.
|
||||||
|
|
||||||
Natural Earth regions and neighborhood polygon lookup are package exports.
|
Natural Earth regions and neighborhood polygon lookup are package exports.
|
||||||
Their existing lazy loaders, retry behavior, bundled datasets and attribution
|
Their existing lazy loaders, retry behavior, bundled datasets and attribution
|
||||||
are unchanged. The cable dataset remains CC BY-NC-SA 3.0 and is not covered by
|
are unchanged. The cable dataset remains CC BY-NC-SA 3.0 and is not covered by
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
|
|||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
import { MAP_STACKS } from '../mapStackController.js';
|
import { MAP_STACKS } from '../mapStackController.js';
|
||||||
|
import { CABLE_LANDING_REVEALS_PER_FRAME } from '../layers/submarineCables/policy.js';
|
||||||
import {
|
import {
|
||||||
CABLE_LABEL_DEPTH_DECISION,
|
CABLE_LABEL_DEPTH_DECISION,
|
||||||
CABLE_OVERLAY_COLLISION_CAPACITY,
|
CABLE_OVERLAY_COLLISION_CAPACITY,
|
||||||
@@ -2003,3 +2004,62 @@ test('every bundled cable segment is associated with its source feature', async
|
|||||||
env.layer.destroy(env.viewer);
|
env.layer.destroy(env.viewer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('landing markers are revealed a few per frame, never all in one', async () => {
|
||||||
|
// Node has no canvas for GeoJSON point pins, so short lines stand in for
|
||||||
|
// the markers (see createRealCableLayerHarness); the staging is the same.
|
||||||
|
const count = CABLE_LANDING_REVEALS_PER_FRAME * 2 + 10;
|
||||||
|
const landingPoints = Array.from({ length: count }, (_, i) => {
|
||||||
|
const lon = -45 + (i % 20) * 0.5;
|
||||||
|
const lat = 30 + Math.floor(i / 20);
|
||||||
|
return {
|
||||||
|
type: 'Feature',
|
||||||
|
id: `landing-${i}`,
|
||||||
|
properties: { id: `landing-${i}`, name: `Landing ${i}` },
|
||||||
|
geometry: {
|
||||||
|
type: 'LineString',
|
||||||
|
coordinates: [
|
||||||
|
[lon, lat],
|
||||||
|
[lon + 0.01, lat + 0.01],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const env = await createRealCableLayerHarness({
|
||||||
|
source: geometrySource([], landingPoints),
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
let renderRequests = 0;
|
||||||
|
env.viewer.scene.requestRender = () => {
|
||||||
|
renderRequests++;
|
||||||
|
};
|
||||||
|
const entities = env.dataSources[1].entities.values;
|
||||||
|
const shown = () => entities.filter((entity) => entity.show).length;
|
||||||
|
assert.equal(entities.length, count);
|
||||||
|
assert.equal(shown(), 0, 'nothing is shown before the first sweep');
|
||||||
|
|
||||||
|
env.raiseSweep();
|
||||||
|
assert.equal(shown(), CABLE_LANDING_REVEALS_PER_FRAME);
|
||||||
|
assert.ok(renderRequests > 0, 'another frame is asked for');
|
||||||
|
env.raiseSweep();
|
||||||
|
assert.equal(shown(), CABLE_LANDING_REVEALS_PER_FRAME * 2);
|
||||||
|
env.raiseSweep();
|
||||||
|
assert.equal(shown(), count, 'every visible marker ends up shown');
|
||||||
|
|
||||||
|
renderRequests = 0;
|
||||||
|
env.raiseSweep();
|
||||||
|
assert.equal(renderRequests, 0, 'a finished reveal asks for no frames');
|
||||||
|
|
||||||
|
// Markers over the horizon are hidden at once and are not revealed.
|
||||||
|
env.viewer.camera.positionWC = Cesium.Cartesian3.fromDegrees(
|
||||||
|
140,
|
||||||
|
-35,
|
||||||
|
4_500_000,
|
||||||
|
);
|
||||||
|
for (const fn of env.listeners.moveEnd) fn();
|
||||||
|
env.raiseSweep();
|
||||||
|
assert.equal(shown(), 0);
|
||||||
|
} finally {
|
||||||
|
env.layer.destroy(env.viewer);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -185,6 +185,10 @@ export function createIngestion({ state, parts, source }) {
|
|||||||
if (!reference) return;
|
if (!reference) return;
|
||||||
|
|
||||||
parts.rendering.styleLandingEntity(entity, feature);
|
parts.rendering.styleLandingEntity(entity, feature);
|
||||||
|
// Hidden until the sweep reveals it: a shown landing marker reads the
|
||||||
|
// terrain height as Cesium creates it, and all of them at once froze
|
||||||
|
// the page (see CABLE_LANDING_REVEALS_PER_FRAME).
|
||||||
|
entity.show = false;
|
||||||
parts.interaction.registerPickEntity(entity, {
|
parts.interaction.registerPickEntity(entity, {
|
||||||
kind: 'landing-point',
|
kind: 'landing-point',
|
||||||
reference,
|
reference,
|
||||||
@@ -193,6 +197,7 @@ export function createIngestion({ state, parts, source }) {
|
|||||||
state._surfaceRecords.push({
|
state._surfaceRecords.push({
|
||||||
entity,
|
entity,
|
||||||
base: Cesium.Cartesian3.fromDegrees(reference.lon, reference.lat, 0),
|
base: Cesium.Cartesian3.fromDegrees(reference.lon, reference.lat, 0),
|
||||||
|
staged: true,
|
||||||
});
|
});
|
||||||
if (landingReferences.has(index)) return;
|
if (landingReferences.has(index)) return;
|
||||||
landingReferences.add(index);
|
landingReferences.add(index);
|
||||||
|
|||||||
@@ -38,12 +38,10 @@ export function createLifecycle({ state, parts, source, mapStackEventTarget }) {
|
|||||||
// never emit moveEnd — reach the motion fallback. No render is
|
// never emit moveEnd — reach the motion fallback. No render is
|
||||||
// requested for a fallback sweep: it only fires while the camera is
|
// requested for a fallback sweep: it only fires while the camera is
|
||||||
// already moving, so frames are flowing by construction.
|
// already moving, so frames are flowing by construction.
|
||||||
if (
|
if (!state._enabled || !state._loaded) return;
|
||||||
!state._enabled ||
|
// Landing markers left over from the last sweep, a few per frame.
|
||||||
!state._loaded ||
|
parts.rendering.revealLandingMarkers();
|
||||||
!state._referenceSweepGate.shouldRun(viewer.camera)
|
if (!state._referenceSweepGate.shouldRun(viewer.camera)) return;
|
||||||
)
|
|
||||||
return;
|
|
||||||
parts.rendering.updateReferenceVisibility();
|
parts.rendering.updateReferenceVisibility();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -66,6 +66,16 @@ export const CABLE_LABEL_DEPTH_DECISION = Object.freeze({
|
|||||||
depthTested: false,
|
depthTested: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Landing markers shown per rendered frame. Each one is a ground-clamped
|
||||||
|
* billboard, and Cesium reads the terrain height when it creates one.
|
||||||
|
* Showing all 1,917 in the frame after the layer was switched on did those
|
||||||
|
* reads in a single task (measured 1.2 s on a loaded globe); a few dozen per
|
||||||
|
* frame keeps each frame short and the markers still fill in within a second.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const CABLE_LANDING_REVEALS_PER_FRAME = 48;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `MAP_STACKS` ids that render imagery on the SHOWN Cesium globe (the ion
|
* `MAP_STACKS` ids that render imagery on the SHOWN Cesium globe (the ion
|
||||||
* Bing stacks + OSM). Deliberately an explicit allowlist, not "anything that
|
* Bing stacks + OSM). Deliberately an explicit allowlist, not "anything that
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
probeTranslucentMarkerBlend,
|
probeTranslucentMarkerBlend,
|
||||||
commitTranslucentMarkerBlend,
|
commitTranslucentMarkerBlend,
|
||||||
} from './surface.js';
|
} from './surface.js';
|
||||||
|
import { CABLE_LANDING_REVEALS_PER_FRAME } from './policy.js';
|
||||||
import {
|
import {
|
||||||
clampLabel,
|
clampLabel,
|
||||||
createCableOverlayEntry,
|
createCableOverlayEntry,
|
||||||
@@ -40,6 +41,7 @@ export function createRendering({ state }) {
|
|||||||
state._referenceDataSource = null;
|
state._referenceDataSource = null;
|
||||||
state._referenceRecords = [];
|
state._referenceRecords = [];
|
||||||
state._surfaceRecords = [];
|
state._surfaceRecords = [];
|
||||||
|
state._pendingLandingReveals.length = 0;
|
||||||
state._pickByEntity = new WeakMap();
|
state._pickByEntity = new WeakMap();
|
||||||
state._referenceLabelCount = 0;
|
state._referenceLabelCount = 0;
|
||||||
state._publishScratch.length = 0;
|
state._publishScratch.length = 0;
|
||||||
@@ -201,6 +203,26 @@ export function createRendering({ state }) {
|
|||||||
state._referenceRecords.push(record);
|
state._referenceRecords.push(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the next few landing markers the last sweep found visible, and ask
|
||||||
|
* for another frame while any are left. Called from the sweep and then once
|
||||||
|
* per rendered frame, so a parked camera costs nothing once the list is
|
||||||
|
* empty.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function revealLandingMarkers() {
|
||||||
|
const pending = state._pendingLandingReveals;
|
||||||
|
if (!pending.length) return;
|
||||||
|
let budget = CABLE_LANDING_REVEALS_PER_FRAME;
|
||||||
|
while (pending.length && budget > 0) {
|
||||||
|
const record = pending.pop();
|
||||||
|
if (!record.visible || record.entity.show) continue;
|
||||||
|
record.entity.show = true;
|
||||||
|
budget--;
|
||||||
|
}
|
||||||
|
if (pending.length) state._viewer?.scene?.requestRender?.();
|
||||||
|
}
|
||||||
|
|
||||||
function updateReferenceVisibility() {
|
function updateReferenceVisibility() {
|
||||||
if (!state._enabled || !state._viewer?.camera) return;
|
if (!state._enabled || !state._viewer?.camera) return;
|
||||||
const cameraPos = state._viewer.camera.positionWC;
|
const cameraPos = state._viewer.camera.positionWC;
|
||||||
@@ -224,12 +246,16 @@ export function createRendering({ state }) {
|
|||||||
if (visible)
|
if (visible)
|
||||||
updateCableReferenceStem(record, cameraPos, canvasHeight, fov);
|
updateCableReferenceStem(record, cameraPos, canvasHeight, fov);
|
||||||
}
|
}
|
||||||
|
state._pendingLandingReveals.length = 0;
|
||||||
for (const record of state._surfaceRecords) {
|
for (const record of state._surfaceRecords) {
|
||||||
const visible = occluder.isPointVisible(record.base);
|
const visible = occluder.isPointVisible(record.base);
|
||||||
if (record.entity.show !== visible) {
|
record.visible = visible;
|
||||||
record.entity.show = visible;
|
if (record.entity.show === visible) continue;
|
||||||
}
|
// Landing markers are shown a few per frame; hiding is immediate.
|
||||||
|
if (visible && record.staged) state._pendingLandingReveals.push(record);
|
||||||
|
else record.entity.show = visible;
|
||||||
}
|
}
|
||||||
|
revealLandingMarkers();
|
||||||
const winners = selectCableReferenceLabelWinners(state._referenceRecords);
|
const winners = selectCableReferenceLabelWinners(state._referenceRecords);
|
||||||
let changed = winners.length !== state._lastPublishedCount;
|
let changed = winners.length !== state._lastPublishedCount;
|
||||||
for (let i = 0; i < winners.length; i++) {
|
for (let i = 0; i < winners.length; i++) {
|
||||||
@@ -269,6 +295,7 @@ export function createRendering({ state }) {
|
|||||||
applyCableClassification,
|
applyCableClassification,
|
||||||
styleLandingEntity,
|
styleLandingEntity,
|
||||||
addReferenceStem,
|
addReferenceStem,
|
||||||
|
revealLandingMarkers,
|
||||||
updateReferenceVisibility,
|
updateReferenceVisibility,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -51,6 +51,11 @@ export function createState({ sweepClock, overlayHost }) {
|
|||||||
|
|
||||||
state._surfaceRecords = [];
|
state._surfaceRecords = [];
|
||||||
|
|
||||||
|
/** Landing records the last sweep found visible but not shown yet; drained
|
||||||
|
* a few per frame (CABLE_LANDING_REVEALS_PER_FRAME). */
|
||||||
|
|
||||||
|
state._pendingLandingReveals = [];
|
||||||
|
|
||||||
state._clickHandler = null;
|
state._clickHandler = null;
|
||||||
|
|
||||||
state._preRenderRemover = null;
|
state._preRenderRemover = null;
|
||||||
|
|||||||
Reference in new issue
Block a user