mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-14 23:32:20 +00:00
Share one render loop fixture across the render failure tests
Four test files built the same widget on CesiumWidget.prototype, viewer proxy, store and notice; renderLoopFixture.mjs now does it once. The notice test drives Cesium.Event instead of a hand-made double. frameEvents() drops tileset.tileLoad, which Cesium raises inside its own error handling, and the notice no longer guards a viewer without a canvas or requestRender.
This commit is contained in:
1 parent
df41c0affb
commit
73bd952736
8 files changed
+127
-252
No files matched your search
@@ -563,6 +563,7 @@
|
|||||||
"src/ui/renderFailureEventRetry.test.mjs",
|
"src/ui/renderFailureEventRetry.test.mjs",
|
||||||
"src/app/renderFailureInlineRetry.test.mjs",
|
"src/app/renderFailureInlineRetry.test.mjs",
|
||||||
"src/ui/renderFailureSecondLoop.test.mjs",
|
"src/ui/renderFailureSecondLoop.test.mjs",
|
||||||
|
"src/ui/renderLoopFixture.mjs",
|
||||||
"src/layers/cctv/lifecycle.js",
|
"src/layers/cctv/lifecycle.js",
|
||||||
"src/layers/cctv/controls.js",
|
"src/layers/cctv/controls.js",
|
||||||
"src/layers/cctv/geometry.js",
|
"src/layers/cctv/geometry.js",
|
||||||
|
|||||||
@@ -5,10 +5,9 @@ import test from 'node:test';
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
import { keepPanelRendering } from './embed.js';
|
import { keepPanelRendering } from './embed.js';
|
||||||
import { installRenderFailureNotice } from '../ui/renderFailure.js';
|
import { renderLoopFixture } from '../ui/renderLoopFixture.mjs';
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
const frames = [];
|
|
||||||
let tick = null;
|
let tick = null;
|
||||||
let clock = 0;
|
let clock = 0;
|
||||||
// The host reports the panel hidden: its own animation frames never arrive.
|
// The host reports the panel hidden: its own animation frames never arrive.
|
||||||
@@ -18,8 +17,6 @@ function setup() {
|
|||||||
setInterval: (fn) => ((tick = fn), 1),
|
setInterval: (fn) => ((tick = fn), 1),
|
||||||
clearInterval() {},
|
clearInterval() {},
|
||||||
};
|
};
|
||||||
const previousRaf = globalThis.requestAnimationFrame;
|
|
||||||
globalThis.requestAnimationFrame = (fn) => frames.push(fn);
|
|
||||||
const previousError = console.error;
|
const previousError = console.error;
|
||||||
console.error = () => {};
|
console.error = () => {};
|
||||||
const state = { failure: null, drawn: 0 };
|
const state = { failure: null, drawn: 0 };
|
||||||
@@ -40,66 +37,33 @@ function setup() {
|
|||||||
this.postRender.raiseEvent(this);
|
this.postRender.raiseEvent(this);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const widget = Object.create(Cesium.CesiumWidget.prototype);
|
const { frames, widget, viewer, store, runFrames, restore } =
|
||||||
Object.assign(widget, {
|
renderLoopFixture(scene, {
|
||||||
_scene: scene,
|
viewer: {
|
||||||
_clock: { tick() {} },
|
isDestroyed: () => false,
|
||||||
_canRender: true,
|
render: () => widget.render(),
|
||||||
_useDefaultRenderLoop: false,
|
resize: () => widget.resize(),
|
||||||
_renderLoopRunning: false,
|
},
|
||||||
_showRenderLoopErrors: false,
|
notice: { allowReload: false },
|
||||||
_targetFrameRate: undefined,
|
});
|
||||||
isDestroyed: () => false,
|
|
||||||
resize() {},
|
|
||||||
});
|
|
||||||
// CesiumWidget's own renderError handler, which marks the loop stopped.
|
|
||||||
scene.renderError.addEventListener(() => {
|
|
||||||
widget._useDefaultRenderLoop = false;
|
|
||||||
widget._renderLoopRunning = false;
|
|
||||||
});
|
|
||||||
const viewer = {
|
|
||||||
scene,
|
|
||||||
cesiumWidget: widget,
|
|
||||||
canvas: { addEventListener() {}, removeEventListener() {} },
|
|
||||||
get useDefaultRenderLoop() {
|
|
||||||
return widget.useDefaultRenderLoop;
|
|
||||||
},
|
|
||||||
set useDefaultRenderLoop(value) {
|
|
||||||
widget.useDefaultRenderLoop = value;
|
|
||||||
},
|
|
||||||
isDestroyed: () => false,
|
|
||||||
render: () => widget.render(),
|
|
||||||
resize: () => widget.resize(),
|
|
||||||
};
|
|
||||||
const store = {
|
|
||||||
renderFailure: null,
|
|
||||||
publishRenderFailure(next) {
|
|
||||||
this.renderFailure = next;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const uninstall = installRenderFailureNotice(viewer, {
|
|
||||||
store,
|
|
||||||
allowReload: false,
|
|
||||||
locationRef: { href: 'https://gev.example/', reload() {}, assign() {} },
|
|
||||||
logError() {},
|
|
||||||
});
|
|
||||||
const stop = keepPanelRendering(viewer, { windowRef, now: () => clock });
|
const stop = keepPanelRendering(viewer, { windowRef, now: () => clock });
|
||||||
viewer.useDefaultRenderLoop = true;
|
viewer.useDefaultRenderLoop = true;
|
||||||
const timerFrame = () => {
|
const timerFrame = () => {
|
||||||
clock += 400;
|
clock += 400;
|
||||||
tick();
|
tick();
|
||||||
};
|
};
|
||||||
const animationFrames = (n) => {
|
return {
|
||||||
for (let frame = 1; frame <= n; frame += 1)
|
frames,
|
||||||
for (const render of frames.splice(0)) render(frame * 16);
|
state,
|
||||||
|
store,
|
||||||
|
timerFrame,
|
||||||
|
animationFrames: runFrames,
|
||||||
|
restore: () => {
|
||||||
|
stop();
|
||||||
|
restore();
|
||||||
|
console.error = previousError;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const restore = () => {
|
|
||||||
stop();
|
|
||||||
uninstall();
|
|
||||||
globalThis.requestAnimationFrame = previousRaf;
|
|
||||||
console.error = previousError;
|
|
||||||
};
|
|
||||||
return { frames, state, store, timerFrame, animationFrames, restore };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const failure of ['caught', 'thrown']) {
|
for (const failure of ['caught', 'thrown']) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function installRenderFailureNotice(
|
|||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
const { scene } = viewer;
|
const { scene } = viewer;
|
||||||
const canvas = viewer.canvas ?? scene.canvas;
|
const { canvas } = viewer;
|
||||||
let notice = null;
|
let notice = null;
|
||||||
let contextLost = false;
|
let contextLost = false;
|
||||||
// With rethrowRenderErrors, Scene throws the error it has just raised;
|
// With rethrowRenderErrors, Scene throws the error it has just raised;
|
||||||
@@ -36,7 +36,7 @@ export function installRenderFailureNotice(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
viewer.useDefaultRenderLoop = true;
|
viewer.useDefaultRenderLoop = true;
|
||||||
scene.requestRender?.();
|
scene.requestRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
@@ -113,11 +113,11 @@ export function installRenderFailureNotice(
|
|||||||
raisedByScene = null;
|
raisedByScene = null;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
canvas?.addEventListener?.('webglcontextlost', onContextLost);
|
canvas.addEventListener('webglcontextlost', onContextLost);
|
||||||
return () => {
|
return () => {
|
||||||
removeRenderError();
|
removeRenderError();
|
||||||
unguard.forEach((restore) => restore());
|
unguard.forEach((restore) => restore());
|
||||||
canvas?.removeEventListener?.('webglcontextlost', onContextLost);
|
canvas.removeEventListener('webglcontextlost', onContextLost);
|
||||||
clear();
|
clear();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -147,7 +147,6 @@ function frameEvents(viewer) {
|
|||||||
tileset?.loadProgress,
|
tileset?.loadProgress,
|
||||||
tileset?.allTilesLoaded,
|
tileset?.allTilesLoaded,
|
||||||
tileset?.initialTilesLoaded,
|
tileset?.initialTilesLoaded,
|
||||||
tileset?.tileLoad,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return events;
|
return events;
|
||||||
|
|||||||
@@ -1,27 +1,14 @@
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
|
import * as Cesium from 'cesium';
|
||||||
import { installRenderFailureNotice } from './renderFailure.js';
|
import { installRenderFailureNotice } from './renderFailure.js';
|
||||||
|
|
||||||
function event() {
|
|
||||||
const listeners = new Set();
|
|
||||||
return {
|
|
||||||
listeners,
|
|
||||||
addEventListener(listener) {
|
|
||||||
listeners.add(listener);
|
|
||||||
return () => listeners.delete(listener);
|
|
||||||
},
|
|
||||||
raise(...args) {
|
|
||||||
for (const listener of [...listeners]) listener(...args);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function fixture(t, options = {}) {
|
function fixture(t, options = {}) {
|
||||||
const canvasListeners = new Map();
|
const canvasListeners = new Map();
|
||||||
const scene = {
|
const scene = {
|
||||||
renderError: event(),
|
renderError: new Cesium.Event(),
|
||||||
preUpdate: event(),
|
preUpdate: new Cesium.Event(),
|
||||||
postRender: event(),
|
postRender: new Cesium.Event(),
|
||||||
requestRender() {
|
requestRender() {
|
||||||
this.requests = (this.requests ?? 0) + 1;
|
this.requests = (this.requests ?? 0) + 1;
|
||||||
},
|
},
|
||||||
@@ -62,12 +49,12 @@ function fixture(t, options = {}) {
|
|||||||
// One Cesium frame: preUpdate, an optional render error, then postRender,
|
// One Cesium frame: preUpdate, an optional render error, then postRender,
|
||||||
// which Cesium raises after a failed frame too.
|
// which Cesium raises after a failed frame too.
|
||||||
const frame = (error) => {
|
const frame = (error) => {
|
||||||
scene.preUpdate.raise(scene);
|
scene.preUpdate.raiseEvent(scene);
|
||||||
if (error) {
|
if (error) {
|
||||||
viewer.useDefaultRenderLoop = false;
|
viewer.useDefaultRenderLoop = false;
|
||||||
scene.renderError.raise(scene, error);
|
scene.renderError.raiseEvent(scene, error);
|
||||||
}
|
}
|
||||||
scene.postRender.raise(scene);
|
scene.postRender.raiseEvent(scene);
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
viewer,
|
viewer,
|
||||||
@@ -105,8 +92,8 @@ test('Retry restarts the render loop and a clean frame clears the notice', (t) =
|
|||||||
f.frame();
|
f.frame();
|
||||||
assert.equal(f.store.renderFailure, null);
|
assert.equal(f.store.renderFailure, null);
|
||||||
// Clearing stops the per-frame listeners.
|
// Clearing stops the per-frame listeners.
|
||||||
assert.equal(f.scene.preUpdate.listeners.size, 0);
|
assert.equal(f.scene.preUpdate.numberOfListeners, 0);
|
||||||
assert.equal(f.scene.postRender.listeners.size, 0);
|
assert.equal(f.scene.postRender.numberOfListeners, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('a loop restarted into the same error keeps one notice', (t) => {
|
test('a loop restarted into the same error keeps one notice', (t) => {
|
||||||
@@ -137,7 +124,7 @@ test('a new failure after recovery is a new appearance', (t) => {
|
|||||||
test('a render error without a value shows no detail', (t) => {
|
test('a render error without a value shows no detail', (t) => {
|
||||||
const f = fixture(t);
|
const f = fixture(t);
|
||||||
f.viewer.useDefaultRenderLoop = false;
|
f.viewer.useDefaultRenderLoop = false;
|
||||||
f.scene.renderError.raise(f.scene, undefined);
|
f.scene.renderError.raiseEvent(f.scene, undefined);
|
||||||
assert.equal(f.store.renderFailure.reason, 'render-error');
|
assert.equal(f.store.renderFailure.reason, 'render-error');
|
||||||
assert.equal(f.store.renderFailure.detail, '');
|
assert.equal(f.store.renderFailure.detail, '');
|
||||||
});
|
});
|
||||||
@@ -179,8 +166,8 @@ test('uninstall removes the listeners and the notice', (t) => {
|
|||||||
f.frame(new Error('boom'));
|
f.frame(new Error('boom'));
|
||||||
f.uninstall();
|
f.uninstall();
|
||||||
assert.equal(f.store.renderFailure, null);
|
assert.equal(f.store.renderFailure, null);
|
||||||
assert.equal(f.scene.renderError.listeners.size, 0);
|
assert.equal(f.scene.renderError.numberOfListeners, 0);
|
||||||
assert.equal(f.scene.postRender.listeners.size, 0);
|
assert.equal(f.scene.postRender.numberOfListeners, 0);
|
||||||
assert.equal(f.canvasListeners.size, 0);
|
assert.equal(f.canvasListeners.size, 0);
|
||||||
f.frame(new Error('later'));
|
f.frame(new Error('later'));
|
||||||
assert.equal(f.store.renderFailure, null);
|
assert.equal(f.store.renderFailure, null);
|
||||||
|
|||||||
@@ -5,11 +5,9 @@ import test from 'node:test';
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
import { installRenderFailureNotice } from './renderFailure.js';
|
import { installRenderFailureNotice } from './renderFailure.js';
|
||||||
|
import { renderLoopFixture } from './renderLoopFixture.mjs';
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
const frames = [];
|
|
||||||
const previousRaf = globalThis.requestAnimationFrame;
|
|
||||||
globalThis.requestAnimationFrame = (fn) => frames.push(fn);
|
|
||||||
// Scene.render as in Scene.js:4369: preUpdate always, preRender/postRender
|
// Scene.render as in Scene.js:4369: preUpdate always, preRender/postRender
|
||||||
// only when a frame is rendered (requestRenderMode idle skips them).
|
// only when a frame is rendered (requestRenderMode idle skips them).
|
||||||
const scene = {
|
const scene = {
|
||||||
@@ -34,53 +32,7 @@ function setup() {
|
|||||||
this.postRender.raiseEvent(this, time);
|
this.postRender.raiseEvent(this, time);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const widget = Object.create(Cesium.CesiumWidget.prototype);
|
return { scene, ...renderLoopFixture(scene) };
|
||||||
Object.assign(widget, {
|
|
||||||
_scene: scene,
|
|
||||||
_clock: { tick() {} },
|
|
||||||
_canRender: true,
|
|
||||||
_useDefaultRenderLoop: false,
|
|
||||||
_renderLoopRunning: false,
|
|
||||||
_showRenderLoopErrors: false,
|
|
||||||
_targetFrameRate: undefined,
|
|
||||||
isDestroyed: () => false,
|
|
||||||
resize() {},
|
|
||||||
});
|
|
||||||
scene.renderError.addEventListener(() => {
|
|
||||||
widget._useDefaultRenderLoop = false;
|
|
||||||
widget._renderLoopRunning = false;
|
|
||||||
});
|
|
||||||
const viewer = {
|
|
||||||
scene,
|
|
||||||
cesiumWidget: widget,
|
|
||||||
canvas: { addEventListener() {}, removeEventListener() {} },
|
|
||||||
get useDefaultRenderLoop() {
|
|
||||||
return widget.useDefaultRenderLoop;
|
|
||||||
},
|
|
||||||
set useDefaultRenderLoop(value) {
|
|
||||||
widget.useDefaultRenderLoop = value;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const store = {
|
|
||||||
renderFailure: null,
|
|
||||||
publishRenderFailure(next) {
|
|
||||||
this.renderFailure = next;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const uninstall = installRenderFailureNotice(viewer, {
|
|
||||||
store,
|
|
||||||
locationRef: { href: 'https://gev.example/', reload() {}, assign() {} },
|
|
||||||
logError() {},
|
|
||||||
});
|
|
||||||
const runFrames = (n) => {
|
|
||||||
for (let i = 0; i < n; i += 1)
|
|
||||||
for (const fn of frames.splice(0)) fn(16 * (i + 1));
|
|
||||||
};
|
|
||||||
const restore = () => {
|
|
||||||
uninstall();
|
|
||||||
globalThis.requestAnimationFrame = previousRaf;
|
|
||||||
};
|
|
||||||
return { frames, scene, widget, viewer, store, runFrames, restore };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const eventName of ['postRender', 'preUpdate']) {
|
for (const eventName of ['postRender', 'preUpdate']) {
|
||||||
@@ -200,7 +152,6 @@ const FRAME_EVENTS = {
|
|||||||
'tileset.allTilesLoaded': (v) => v.scene.primitives.get(0).allTilesLoaded,
|
'tileset.allTilesLoaded': (v) => v.scene.primitives.get(0).allTilesLoaded,
|
||||||
'tileset.initialTilesLoaded': (v) =>
|
'tileset.initialTilesLoaded': (v) =>
|
||||||
v.scene.primitives.get(0).initialTilesLoaded,
|
v.scene.primitives.get(0).initialTilesLoaded,
|
||||||
'tileset.tileLoad': (v) => v.scene.primitives.get(0).tileLoad,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [name, pick] of Object.entries(FRAME_EVENTS)) {
|
for (const [name, pick] of Object.entries(FRAME_EVENTS)) {
|
||||||
@@ -214,7 +165,6 @@ for (const [name, pick] of Object.entries(FRAME_EVENTS)) {
|
|||||||
loadProgress: new Cesium.Event(),
|
loadProgress: new Cesium.Event(),
|
||||||
allTilesLoaded: new Cesium.Event(),
|
allTilesLoaded: new Cesium.Event(),
|
||||||
initialTilesLoaded: new Cesium.Event(),
|
initialTilesLoaded: new Cesium.Event(),
|
||||||
tileLoad: new Cesium.Event(),
|
|
||||||
};
|
};
|
||||||
const viewer = {
|
const viewer = {
|
||||||
scene: {
|
scene: {
|
||||||
|
|||||||
@@ -3,66 +3,21 @@
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
import { installRenderFailureNotice } from './renderFailure.js';
|
import { renderLoopFixture } from './renderLoopFixture.mjs';
|
||||||
|
|
||||||
// What src/app/scene.js passes to the viewer (showRenderLoopErrors).
|
|
||||||
const SHOW_RENDER_LOOP_ERRORS = false;
|
|
||||||
|
|
||||||
function runLoop(render) {
|
function runLoop(render) {
|
||||||
const frames = [];
|
const scene = {
|
||||||
const previousRaf = globalThis.requestAnimationFrame;
|
renderError: new Cesium.Event(),
|
||||||
globalThis.requestAnimationFrame = (fn) => frames.push(fn);
|
preUpdate: new Cesium.Event(),
|
||||||
try {
|
postRender: new Cesium.Event(),
|
||||||
const scene = {
|
requestRender() {},
|
||||||
renderError: new Cesium.Event(),
|
initializeFrame() {},
|
||||||
preUpdate: new Cesium.Event(),
|
render,
|
||||||
postRender: new Cesium.Event(),
|
};
|
||||||
requestRender() {},
|
const fixture = renderLoopFixture(scene);
|
||||||
initializeFrame() {},
|
fixture.viewer.useDefaultRenderLoop = true;
|
||||||
render,
|
fixture.runFrames(3);
|
||||||
};
|
return { ...fixture, uninstall: fixture.restore };
|
||||||
const widget = Object.create(Cesium.CesiumWidget.prototype);
|
|
||||||
Object.assign(widget, {
|
|
||||||
_scene: scene,
|
|
||||||
_clock: { tick() {} },
|
|
||||||
_canRender: true,
|
|
||||||
_useDefaultRenderLoop: false,
|
|
||||||
_renderLoopRunning: false,
|
|
||||||
_showRenderLoopErrors: SHOW_RENDER_LOOP_ERRORS,
|
|
||||||
_targetFrameRate: undefined,
|
|
||||||
isDestroyed: () => false,
|
|
||||||
resize() {},
|
|
||||||
showErrorPanel() {},
|
|
||||||
});
|
|
||||||
const viewer = {
|
|
||||||
scene,
|
|
||||||
cesiumWidget: widget,
|
|
||||||
canvas: { addEventListener() {}, removeEventListener() {} },
|
|
||||||
get useDefaultRenderLoop() {
|
|
||||||
return widget.useDefaultRenderLoop;
|
|
||||||
},
|
|
||||||
set useDefaultRenderLoop(value) {
|
|
||||||
widget.useDefaultRenderLoop = value;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const store = {
|
|
||||||
renderFailure: null,
|
|
||||||
publishRenderFailure(next) {
|
|
||||||
this.renderFailure = next;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const logged = [];
|
|
||||||
const uninstall = installRenderFailureNotice(viewer, {
|
|
||||||
store,
|
|
||||||
locationRef: { href: 'https://gev.example/', reload() {}, assign() {} },
|
|
||||||
logError: (error) => logged.push(error),
|
|
||||||
});
|
|
||||||
viewer.useDefaultRenderLoop = true;
|
|
||||||
while (frames.length) frames.shift()(16);
|
|
||||||
return { widget, store, logged, uninstall };
|
|
||||||
} finally {
|
|
||||||
globalThis.requestAnimationFrame = previousRaf;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test('an error thrown outside tryAndCatchError still shows the notice', () => {
|
test('an error thrown outside tryAndCatchError still shows the notice', () => {
|
||||||
|
|||||||
@@ -4,12 +4,9 @@
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
import { installRenderFailureNotice } from './renderFailure.js';
|
import { renderLoopFixture } from './renderLoopFixture.mjs';
|
||||||
|
|
||||||
function setup({ targetFrameRate } = {}) {
|
function setup({ targetFrameRate } = {}) {
|
||||||
const frames = [];
|
|
||||||
const previousRaf = globalThis.requestAnimationFrame;
|
|
||||||
globalThis.requestAnimationFrame = (fn) => frames.push(fn);
|
|
||||||
const scene = {
|
const scene = {
|
||||||
renderError: new Cesium.Event(),
|
renderError: new Cesium.Event(),
|
||||||
preUpdate: new Cesium.Event(),
|
preUpdate: new Cesium.Event(),
|
||||||
@@ -29,57 +26,7 @@ function setup({ targetFrameRate } = {}) {
|
|||||||
this.postRender.raiseEvent(this, time);
|
this.postRender.raiseEvent(this, time);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const widget = Object.create(Cesium.CesiumWidget.prototype);
|
return { scene, ...renderLoopFixture(scene, { targetFrameRate }) };
|
||||||
Object.assign(widget, {
|
|
||||||
_scene: scene,
|
|
||||||
_clock: { tick() {} },
|
|
||||||
_canRender: true,
|
|
||||||
_useDefaultRenderLoop: false,
|
|
||||||
_renderLoopRunning: false,
|
|
||||||
_showRenderLoopErrors: false,
|
|
||||||
_targetFrameRate: targetFrameRate,
|
|
||||||
isDestroyed: () => false,
|
|
||||||
resize() {},
|
|
||||||
});
|
|
||||||
// CesiumWidget's own renderError handler (CesiumWidget.js:414).
|
|
||||||
scene.renderError.addEventListener(() => {
|
|
||||||
widget._useDefaultRenderLoop = false;
|
|
||||||
widget._renderLoopRunning = false;
|
|
||||||
});
|
|
||||||
const viewer = {
|
|
||||||
scene,
|
|
||||||
cesiumWidget: widget,
|
|
||||||
canvas: { addEventListener() {}, removeEventListener() {} },
|
|
||||||
get useDefaultRenderLoop() {
|
|
||||||
return widget.useDefaultRenderLoop;
|
|
||||||
},
|
|
||||||
set useDefaultRenderLoop(value) {
|
|
||||||
widget.useDefaultRenderLoop = value;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const store = {
|
|
||||||
renderFailure: null,
|
|
||||||
publishRenderFailure(next) {
|
|
||||||
this.renderFailure = next;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const uninstall = installRenderFailureNotice(viewer, {
|
|
||||||
store,
|
|
||||||
locationRef: { href: 'https://gev.example/', reload() {}, assign() {} },
|
|
||||||
logError() {},
|
|
||||||
});
|
|
||||||
let time = 0;
|
|
||||||
const runFrames = (n) => {
|
|
||||||
for (let i = 0; i < n; i += 1) {
|
|
||||||
time += 17;
|
|
||||||
for (const fn of frames.splice(0)) fn(time);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const restore = () => {
|
|
||||||
uninstall();
|
|
||||||
globalThis.requestAnimationFrame = previousRaf;
|
|
||||||
};
|
|
||||||
return { frames, scene, viewer, store, runFrames, restore };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const targetFrameRate of [undefined, 60]) {
|
for (const targetFrameRate of [undefined, 60]) {
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// A widget on Cesium's real CesiumWidget.prototype (startRenderLoop, the
|
||||||
|
// useDefaultRenderLoop setter, render) around a scene double, with animation
|
||||||
|
// frames queued by hand and the render failure notice installed.
|
||||||
|
import * as Cesium from 'cesium';
|
||||||
|
import { installRenderFailureNotice } from './renderFailure.js';
|
||||||
|
|
||||||
|
export function renderLoopFixture(
|
||||||
|
scene,
|
||||||
|
{ targetFrameRate, viewer: viewerExtras = {}, notice = {} } = {},
|
||||||
|
) {
|
||||||
|
const frames = [];
|
||||||
|
const previousRaf = globalThis.requestAnimationFrame;
|
||||||
|
globalThis.requestAnimationFrame = (fn) => frames.push(fn);
|
||||||
|
const widget = Object.create(Cesium.CesiumWidget.prototype);
|
||||||
|
Object.assign(widget, {
|
||||||
|
_scene: scene,
|
||||||
|
_clock: { tick() {} },
|
||||||
|
_canRender: true,
|
||||||
|
_useDefaultRenderLoop: false,
|
||||||
|
_renderLoopRunning: false,
|
||||||
|
// What src/app/scene.js passes to the viewer.
|
||||||
|
_showRenderLoopErrors: false,
|
||||||
|
_targetFrameRate: targetFrameRate,
|
||||||
|
isDestroyed: () => false,
|
||||||
|
resize() {},
|
||||||
|
});
|
||||||
|
// CesiumWidget's own renderError handler, which marks the loop stopped.
|
||||||
|
scene.renderError.addEventListener(() => {
|
||||||
|
widget._useDefaultRenderLoop = false;
|
||||||
|
widget._renderLoopRunning = false;
|
||||||
|
});
|
||||||
|
const viewer = Object.assign(
|
||||||
|
{
|
||||||
|
scene,
|
||||||
|
cesiumWidget: widget,
|
||||||
|
canvas: { addEventListener() {}, removeEventListener() {} },
|
||||||
|
get useDefaultRenderLoop() {
|
||||||
|
return widget.useDefaultRenderLoop;
|
||||||
|
},
|
||||||
|
set useDefaultRenderLoop(value) {
|
||||||
|
widget.useDefaultRenderLoop = value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
viewerExtras,
|
||||||
|
);
|
||||||
|
const store = {
|
||||||
|
renderFailure: null,
|
||||||
|
publishRenderFailure(next) {
|
||||||
|
this.renderFailure = next;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const logged = [];
|
||||||
|
const uninstall = installRenderFailureNotice(viewer, {
|
||||||
|
store,
|
||||||
|
locationRef: { href: 'https://gev.example/', reload() {}, assign() {} },
|
||||||
|
logError: (error) => logged.push(error),
|
||||||
|
...notice,
|
||||||
|
});
|
||||||
|
let time = 0;
|
||||||
|
const runFrames = (n) => {
|
||||||
|
for (let i = 0; i < n; i += 1) {
|
||||||
|
// Above a 60 fps target's interval, so every frame draws.
|
||||||
|
time += 17;
|
||||||
|
for (const fn of frames.splice(0)) fn(time);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const restore = () => {
|
||||||
|
uninstall();
|
||||||
|
globalThis.requestAnimationFrame = previousRaf;
|
||||||
|
};
|
||||||
|
return { frames, widget, viewer, store, logged, runFrames, restore };
|
||||||
|
}
|
||||||
Reference in new issue
Block a user