From 2bb0b1e367cd0d515859520f7715707031d312d6 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Fri, 6 Nov 2026 17:16:18 -0800 Subject: [PATCH] Set scene time through the Cesium frame port, in the application too frame.setTime moves from the conformance renderer into the shared Cesium ports, so the application's renderer has it. It moves the clock and asks for a frame, which on-demand drawing needs. It no longer stops the clock: the conformance renderer stops its own at creation, and the application's clock keeps whatever state its owner gave it. --- src/render/cesium/appRenderer.js | 9 ++++----- src/render/cesium/appRenderer.test.mjs | 1 - src/render/cesium/ports.js | 10 ++++++++++ src/render/cesium/ports.test.mjs | 15 +++++++++++++++ src/render/cesium/sceneRenderer.js | 13 +++---------- 5 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/render/cesium/appRenderer.js b/src/render/cesium/appRenderer.js index 2e87091b1..df11714bd 100644 --- a/src/render/cesium/appRenderer.js +++ b/src/render/cesium/appRenderer.js @@ -7,11 +7,10 @@ * The renderer it makes is transitional. Its ports are the shared Cesium * ports (`ports.js`), the ones the conformance renderer is checked with, * less the members that would not be true of the application yet: the - * globe (it draws photoreal tiles the port does not know), `whenSettled` - * (it would not wait for them), `frame.setTime` (it would stop the - * application's clock). Those arrive with the layer families that need - * them (phase 2 of the renderer plan); `checkRendererShape` lists what is - * still missing. + * globe (it draws photoreal tiles the port does not know) and + * `whenSettled` (it would not wait for them). Those arrive with the layer + * families that need them (phase 2 of the renderer plan); + * `checkRendererShape` lists what is still missing. * Until a family is ported, its code reaches the Cesium viewer through * `cesiumViewerOf`, never through a renderer member, so no engine type * crosses the contract and each call marks code still to port. Ported code diff --git a/src/render/cesium/appRenderer.test.mjs b/src/render/cesium/appRenderer.test.mjs index eb0cf71a0..b351b8d70 100644 --- a/src/render/cesium/appRenderer.test.mjs +++ b/src/render/cesium/appRenderer.test.mjs @@ -318,7 +318,6 @@ test('the application refuses a renderer it cannot run on, and destroys it', asy }); const EXPECTED_MISSING = [ - 'frame.setTime', 'renderer.globe', 'renderer.readPixels', 'renderer.releaseUnused', diff --git a/src/render/cesium/ports.js b/src/render/cesium/ports.js index 3a18938c4..4fd652a1b 100644 --- a/src/render/cesium/ports.js +++ b/src/render/cesium/ports.js @@ -347,6 +347,16 @@ export function createCesiumPorts(widget, { capabilities }) { targetFps = fps > 0 ? fps : 0; widget.targetFrameRate = targetFps || undefined; }, + /** + * Moves the clock to `ms` and draws it. Whether the clock then runs on + * is its owner's: the application's clock keeps running if it was (a + * clock on Cesium's real-time step moves to its multiplier step, as + * setting its time does in Cesium). + */ + setTime(ms) { + widget.clock.currentTime = Cesium.JulianDate.fromDate(new Date(ms)); + frame.requestRender('time'); + }, time: () => Cesium.JulianDate.toDate(widget.clock.currentTime).getTime(), }; diff --git a/src/render/cesium/ports.test.mjs b/src/render/cesium/ports.test.mjs index edd276e23..f2d4c571b 100644 --- a/src/render/cesium/ports.test.mjs +++ b/src/render/cesium/ports.test.mjs @@ -697,3 +697,18 @@ test('an icon draws its atlas slot once the atlas has it, and again when it chan ); ports.dispose(); }); + +test('setTime moves scene time and draws it, leaving the clock running or not', () => { + const { widget, scene } = tickingWidget(); + let requested = 0; + scene.requestRender = () => (requested += 1); + widget.clock.shouldAnimate = true; + const ports = createCesiumPorts(widget, { capabilities: all }); + const t = Date.UTC(2026, 5, 21, 12); + ports.frame.setTime(t); + assert.equal(ports.frame.time(), t); + assert.equal(widget.clock.shouldAnimate, true, 'still running'); + assert.equal(requested, 1, 'one frame asked for'); + assert.deepEqual(ports.frame.diagnostics().pendingReasons, ['time']); + ports.dispose(); +}); diff --git a/src/render/cesium/sceneRenderer.js b/src/render/cesium/sceneRenderer.js index 68514b986..1fa54cb4a 100644 --- a/src/render/cesium/sceneRenderer.js +++ b/src/render/cesium/sceneRenderer.js @@ -66,7 +66,8 @@ export async function createCesiumSceneRenderer({ contextOptions: { webgl: { preserveDrawingBuffer: true } }, }); const scene = widget.scene; - // A fixed time, so lighting is the same in every capture. + // A fixed time, so lighting is the same in every capture; `setTime` + // moves it and it stays there. widget.clock.shouldAnimate = false; widget.clock.currentTime = Cesium.JulianDate.fromIso8601( '2026-06-21T18:00:00Z', @@ -95,14 +96,6 @@ export async function createCesiumSceneRenderer({ query: ports.query, report: ports.report, }); - const frame = { - ...ports.frame, - setTime(ms) { - widget.clock.currentTime = Cesium.JulianDate.fromDate(new Date(ms)); - widget.clock.shouldAnimate = false; - scene.requestRender(); - }, - }; // --- Globe ----------------------------------------------------------------- @@ -269,7 +262,7 @@ export async function createCesiumSceneRenderer({ contractVersion: '0', capabilities, canvas: scene.canvas, - frame, + frame: ports.frame, globe, // A member of a capability left out is absent, not stubbed. draw: capabilities.has('models')