Compare commits

...
Author SHA1 Message Date
Mazeyar Moeini FeizabadiandClaude Fable 5.2 b6c241a68e docs(changelog): add the #1707 fragment
Co-Authored-By: Claude Fable 5.2 <[email protected]>
2026-11-13 16:05:57 +01:00
Mazeyar Moeini FeizabadiandClaude Fable 5.2 f42980a04d 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]>
2026-11-13 15:05:54 +01:00
8 changed files with 123 additions and 9 deletions

No files matched your search

@@ -0,0 +1,3 @@
- Shorten the freeze when Submarine cables is switched on from about 1.4 s to
about 0.2 s. Landing markers are now shown a few dozen per frame instead of
all at once (mazy1998, #1707).
+6
View File
@@ -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
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.
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
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import * as Cesium from 'cesium';
import { MAP_STACKS } from '../mapStackController.js';
import { CABLE_LANDING_REVEALS_PER_FRAME } from '../layers/submarineCables/policy.js';
import {
CABLE_LABEL_DEPTH_DECISION,
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);
}
});
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);
}
});
+5
View File
@@ -185,6 +185,10 @@ export function createIngestion({ state, parts, source }) {
if (!reference) return;
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, {
kind: 'landing-point',
reference,
@@ -193,6 +197,7 @@ export function createIngestion({ state, parts, source }) {
state._surfaceRecords.push({
entity,
base: Cesium.Cartesian3.fromDegrees(reference.lon, reference.lat, 0),
staged: true,
});
if (landingReferences.has(index)) return;
landingReferences.add(index);
+4 -6
View File
@@ -38,12 +38,10 @@ export function createLifecycle({ state, parts, source, mapStackEventTarget }) {
// never emit moveEnd — reach the motion fallback. No render is
// requested for a fallback sweep: it only fires while the camera is
// already moving, so frames are flowing by construction.
if (
!state._enabled ||
!state._loaded ||
!state._referenceSweepGate.shouldRun(viewer.camera)
)
return;
if (!state._enabled || !state._loaded) return;
// Landing markers left over from the last sweep, a few per frame.
parts.rendering.revealLandingMarkers();
if (!state._referenceSweepGate.shouldRun(viewer.camera)) return;
parts.rendering.updateReferenceVisibility();
},
);
+10
View File
@@ -66,6 +66,16 @@ export const CABLE_LABEL_DEPTH_DECISION = Object.freeze({
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
* Bing stacks + OSM). Deliberately an explicit allowlist, not "anything that
+30 -3
View File
@@ -3,6 +3,7 @@ import {
probeTranslucentMarkerBlend,
commitTranslucentMarkerBlend,
} from './surface.js';
import { CABLE_LANDING_REVEALS_PER_FRAME } from './policy.js';
import {
clampLabel,
createCableOverlayEntry,
@@ -40,6 +41,7 @@ export function createRendering({ state }) {
state._referenceDataSource = null;
state._referenceRecords = [];
state._surfaceRecords = [];
state._pendingLandingReveals.length = 0;
state._pickByEntity = new WeakMap();
state._referenceLabelCount = 0;
state._publishScratch.length = 0;
@@ -201,6 +203,26 @@ export function createRendering({ state }) {
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() {
if (!state._enabled || !state._viewer?.camera) return;
const cameraPos = state._viewer.camera.positionWC;
@@ -224,12 +246,16 @@ export function createRendering({ state }) {
if (visible)
updateCableReferenceStem(record, cameraPos, canvasHeight, fov);
}
state._pendingLandingReveals.length = 0;
for (const record of state._surfaceRecords) {
const visible = occluder.isPointVisible(record.base);
if (record.entity.show !== visible) {
record.entity.show = visible;
}
record.visible = 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);
let changed = winners.length !== state._lastPublishedCount;
for (let i = 0; i < winners.length; i++) {
@@ -269,6 +295,7 @@ export function createRendering({ state }) {
applyCableClassification,
styleLandingEntity,
addReferenceStem,
revealLandingMarkers,
updateReferenceVisibility,
};
}
+5
View File
@@ -51,6 +51,11 @@ export function createState({ sweepClock, overlayHost }) {
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._preRenderRemover = null;