mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
fix(globe): render holds and commanded on Navara
The render governor installs on a non-Cesium globe engine as its backend: owner-counted holds drive engine.render.setContinuous and requests go to engine.render.request, so orbit and every other per-frame owner keep Navara drawing while held and let it idle once the last hold goes. Cesium stays on the viewer path its engine delegates to. A user gesture on Navara now ends a flight without raising camera.commanded (the user is not a new camera owner). Headless Navara QA checks both.
This commit is contained in:
1 parent
c6e1b65c67
commit
d112ecf296
5 files changed
+81
-5
No files matched your search
@@ -389,6 +389,11 @@ export function createCameraModel({
|
||||
camera,
|
||||
sync,
|
||||
afterFrame,
|
||||
/**
|
||||
* Ends a running flight (its `cancel` runs) without camera.commanded:
|
||||
* for the user's own gestures, which are not a new camera owner.
|
||||
*/
|
||||
stopFlight: () => cancelCurrentFlight(),
|
||||
/** The current camera for projections: pose, vertical fov and CSS size. */
|
||||
view,
|
||||
worldToWindow: (position, result) =>
|
||||
|
||||
@@ -120,7 +120,8 @@ export function createNavaraEngine(
|
||||
view.on(name, handler);
|
||||
|
||||
const input = createNavaraInput(view, {
|
||||
onCameraInput: () => model.camera.cancelFlight(),
|
||||
// A gesture is the user, not a new camera owner: no camera.commanded.
|
||||
onCameraInput: () => model.stopFlight(),
|
||||
});
|
||||
const credits = createNavaraCredits(view);
|
||||
const maps = createNavaraMaps(view, fetch ? { fetch } : {});
|
||||
|
||||
@@ -226,3 +226,20 @@ test('pickGlobe meets the terrain along the pick ray, or the ellipsoid', () => {
|
||||
near(math.toDegrees(place.longitude), 0, 1e-6, 'longitude');
|
||||
assert.equal(engine.picker.pickGlobe(cartesian2.create(640, -1e7)), undefined);
|
||||
});
|
||||
|
||||
test('a user gesture cancels a flight without raising camera.commanded', () => {
|
||||
const { engine, view } = setup();
|
||||
const outcome = [];
|
||||
let commanded = 0;
|
||||
engine.camera.flyTo({
|
||||
destination: cartesian3.fromDegrees(10, 10, 1e6),
|
||||
duration: 5,
|
||||
cancel: () => outcome.push('cancel'),
|
||||
});
|
||||
engine.camera.commanded.addEventListener(() => commanded++);
|
||||
view.canvas.dispatchEvent(new Event('pointerdown'));
|
||||
view.canvas.dispatchEvent(new Event('wheel'));
|
||||
assert.deepEqual(outcome, ['cancel']);
|
||||
assert.equal(engine.camera.flightActive(), false);
|
||||
assert.equal(commanded, 0, 'a gesture is the user, not a new camera owner');
|
||||
});
|
||||
+31
-4
@@ -36,9 +36,17 @@
|
||||
* cycle) and keeps the viewer for what the contract leaves to the engine:
|
||||
* `scene.requestRenderMode`, `scene.maximumRenderTimeChange` and
|
||||
* `scene.requestRender()`. It needs no frame events (`engine.frames`).
|
||||
*
|
||||
* Any other globe engine (Navara) is the governor's backend instead: install
|
||||
* it on the engine and the owner-counted holds drive
|
||||
* `engine.render.setContinuous`, requests go to `engine.render.request`.
|
||||
*/
|
||||
|
||||
import { isGlobeEngine } from './globe/contract.js';
|
||||
|
||||
let _viewer = null;
|
||||
/** The non-Cesium engine the governor drives, or null (Cesium: `_viewer`). */
|
||||
let _engine = null;
|
||||
let _installed = false;
|
||||
const _holds = new Set();
|
||||
|
||||
@@ -47,8 +55,15 @@ const _recentRequests = [];
|
||||
const RECENT_REQUEST_CAP = 16;
|
||||
|
||||
function applyMode() {
|
||||
if (!_installed || !_viewer?.scene) return;
|
||||
if (!_installed) return;
|
||||
const continuous = _holds.size > 0;
|
||||
if (_engine) {
|
||||
if (_engine.render.isContinuous() !== continuous)
|
||||
_engine.render.setContinuous(continuous);
|
||||
if (!continuous) _engine.render.request('governor-idle');
|
||||
return;
|
||||
}
|
||||
if (!_viewer?.scene) return;
|
||||
const scene = _viewer.scene;
|
||||
if (scene.requestRenderMode === !continuous) return;
|
||||
scene.requestRenderMode = !continuous;
|
||||
@@ -64,12 +79,21 @@ function applyMode() {
|
||||
* hold/release still record into the holds set (and apply at install time);
|
||||
* requests are safe no-ops — so modules can call all three unconditionally
|
||||
* in tests without a viewer.
|
||||
* @param {Cesium.Viewer} viewer
|
||||
* @param {Cesium.Viewer | import('./globe/contract.js').GlobeEngine} viewer
|
||||
* The Cesium viewer, or a non-Cesium globe engine.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function installRenderGovernor(viewer) {
|
||||
if (isGlobeEngine(viewer) && viewer.kind !== 'cesium') {
|
||||
_engine = viewer;
|
||||
_viewer = viewer;
|
||||
_installed = true;
|
||||
applyMode();
|
||||
return;
|
||||
}
|
||||
if (!viewer?.scene)
|
||||
throw new TypeError('installRenderGovernor requires a Cesium viewer');
|
||||
_engine = null;
|
||||
_viewer = viewer;
|
||||
_installed = true;
|
||||
// Never let Cesium re-render on simulation-time deltas behind our back —
|
||||
@@ -115,12 +139,13 @@ export function releaseContinuousRender(ownerId) {
|
||||
* @returns {void}
|
||||
*/
|
||||
export function governorRequestRender(reason = 'unspecified') {
|
||||
if (!_installed || !_viewer?.scene) return;
|
||||
if (!_installed || (!_engine && !_viewer?.scene)) return;
|
||||
if (_holds.size === 0) {
|
||||
_recentRequests.push({ reason, at: Date.now() });
|
||||
if (_recentRequests.length > RECENT_REQUEST_CAP) _recentRequests.shift();
|
||||
}
|
||||
_viewer.scene.requestRender?.();
|
||||
if (_engine) _engine.render.request(reason);
|
||||
else _viewer.scene.requestRender?.();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,6 +175,7 @@ export function isRenderGovernorInstalled(viewer) {
|
||||
export function uninstallRenderGovernor(viewer) {
|
||||
if (_viewer !== viewer) return;
|
||||
_viewer = null;
|
||||
_engine = null;
|
||||
_installed = false;
|
||||
_holds.clear();
|
||||
_recentRequests.length = 0;
|
||||
@@ -158,6 +184,7 @@ export function uninstallRenderGovernor(viewer) {
|
||||
/** Test seam: reset module state between unit tests. */
|
||||
export function _resetRenderGovernorForTest() {
|
||||
_viewer = null;
|
||||
_engine = null;
|
||||
_installed = false;
|
||||
_holds.clear();
|
||||
_recentRequests.length = 0;
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
governorRequestRender,
|
||||
getRenderGovernorDiagnostics,
|
||||
_resetRenderGovernorForTest,
|
||||
isRenderGovernorInstalled,
|
||||
uninstallRenderGovernor,
|
||||
} from './renderGovernor.js';
|
||||
|
||||
function makeViewer() {
|
||||
@@ -110,3 +112,27 @@ test('isRenderGovernorInstalled names the one governed viewer', async () => {
|
||||
uninstallRenderGovernor(viewer);
|
||||
assert.equal(isRenderGovernorInstalled(viewer), false);
|
||||
});
|
||||
|
||||
test('on a non-Cesium globe engine the governor drives engine.render with owner-counted holds', async () => {
|
||||
const { createFakeEngine } = await import('./globe/testing/fakeEngine.js');
|
||||
_resetRenderGovernorForTest();
|
||||
const engine = createFakeEngine({ kind: 'navara' });
|
||||
holdContinuousRender('before-install');
|
||||
installRenderGovernor(engine);
|
||||
assert.equal(engine.render.isContinuous(), true, 'a hold taken before install applies');
|
||||
releaseContinuousRender('before-install');
|
||||
assert.equal(engine.render.isContinuous(), false);
|
||||
holdContinuousRender('a');
|
||||
holdContinuousRender('b');
|
||||
assert.equal(engine.render.isContinuous(), true);
|
||||
releaseContinuousRender('a');
|
||||
assert.equal(engine.render.isContinuous(), true, 'b still holds');
|
||||
releaseContinuousRender('b');
|
||||
assert.equal(engine.render.isContinuous(), false);
|
||||
engine.fake.resetCalls();
|
||||
governorRequestRender('slider');
|
||||
assert.deepEqual(engine.fake.callsOf('render.request').map((c) => c.args), [['slider']]);
|
||||
assert.equal(isRenderGovernorInstalled(engine), true);
|
||||
uninstallRenderGovernor(engine);
|
||||
_resetRenderGovernorForTest();
|
||||
});
|
||||
Reference in new issue
Block a user