mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-13 23:02:22 +00:00
Shorten the render failure notice's comments and docs
Comments keep only the why: the loop-running flag, the mid-raise events and the one-time canvas focus. The CURRENT-STATE section lists behaviour and pitfalls instead of retelling the code, and no longer says every tileset event is raised outside Scene's error handling.
This commit is contained in:
1 parent
d42b6fe764
commit
df41c0affb
7 files changed
+74
-120
No files matched your search
+38
-57
@@ -2445,66 +2445,47 @@ during viewer construction, before any tile builds a draw command.
|
||||
## Render failure notice
|
||||
|
||||
When the globe stops drawing, the scene chrome says so instead of leaving a
|
||||
black globe behind controls that still respond. `installRenderFailureNotice()`
|
||||
(`src/ui/renderFailure.js`) listens for Cesium's `renderError`, after which
|
||||
Cesium stops its render loop, and for the canvas' `webglcontextlost`. It logs
|
||||
the error and publishes a notice to the status store, which
|
||||
`RenderFailure.vue` renders as an alert dialog that takes focus. When the
|
||||
globe draws again, focus goes back to the element that had it, or, if that
|
||||
element is gone or cannot take focus, to the globe's canvas, which the map's
|
||||
keys (Enter to finish a drawing, Space to talk) read as the map. The canvas
|
||||
gets `tabindex="-1"` only until focus moves on or the globe is pressed, and a
|
||||
press leaves focus on the page, as Cesium has it. Focus the user moved
|
||||
meanwhile stays put. The
|
||||
application scene creates its viewer with `showRenderLoopErrors: false`, so
|
||||
Cesium's own "Rendering has stopped" panel, which offers no way back, no longer
|
||||
appears; `applicationViewerOptions()` and `createApplicationViewer()` keep
|
||||
Cesium's panel by default for other callers. An error thrown outside Scene's
|
||||
own error handling (a `preRender` or `postRender` listener, the clock's
|
||||
`onTick`, a resize) also stops the loop without a `renderError`, so the notice
|
||||
wraps the widget's `render()` and `resize()` and raises such an error as a
|
||||
render error before Cesium's loop handles it as before; one Scene has raised
|
||||
already and throws on (`rethrowRenderErrors`) is not raised again. A lost
|
||||
context fails every later frame, and those errors are only logged, so the
|
||||
notice keeps saying why. The notice sits above floating panels such as the
|
||||
Québec camera viewer, and its buttons are 44 px tall on touch screens.
|
||||
black globe behind working controls. `installRenderFailureNotice()`
|
||||
(`src/ui/renderFailure.js`) listens for Cesium's `renderError` and the canvas'
|
||||
`webglcontextlost`, logs the error, and publishes a notice that
|
||||
`RenderFailure.vue` shows as an alert dialog with focus on its first action.
|
||||
The app's viewer uses `showRenderLoopErrors: false`, so Cesium's dead-end
|
||||
"Rendering has stopped" panel no longer appears; `applicationViewerOptions()`
|
||||
keeps it by default for other callers.
|
||||
|
||||
- **Retry** after a render error restarts the render loop. The notice stays
|
||||
until a frame starts and ends without a render error (Cesium raises
|
||||
`postRender` after a failed frame too), and a loop that fails again keeps it.
|
||||
A listener that throws leaves its Cesium event mid-raise, with listeners
|
||||
added or removed since only queued, so the `render()` and `resize()` wrapper
|
||||
finishes the events it cut short (`finishRaise()`) before it raises the
|
||||
render error. These are the events Cesium raises outside Scene's error
|
||||
handling (`frameEvents()`): Scene's frame events, the camera's and the
|
||||
clock's, the globe's tile load progress, and the load events of the scene's
|
||||
top-level tilesets. So the notice's frame watchers start with the next
|
||||
frame, a listener removed while the loop is stopped, for a layer turned off,
|
||||
is not called again, and one turned off and on again keeps running.
|
||||
- **Reload** after a lost context reloads the page: Cesium cannot draw into a
|
||||
context it has lost, and the address keeps the view.
|
||||
- **Reset view** opens the app's root address (`appRootUrl()`), without the
|
||||
hash or `/share/<code>` path the view was restored from; the query, such as
|
||||
`?lang=`, is kept.
|
||||
- **Retry** restarts the render loop. The notice stays until a frame starts
|
||||
and ends without an error: Cesium raises `postRender` after a failed frame
|
||||
too.
|
||||
- **Reload**, after a lost context, reloads the page; the address keeps the
|
||||
view.
|
||||
- **Reset view** opens `appRootUrl()`, without the hash or `/share/<code>`
|
||||
path; the query, such as `?lang=`, is kept.
|
||||
|
||||
An inline conversation panel never reloads or navigates its host's page, so it
|
||||
Pitfalls:
|
||||
|
||||
- An error thrown outside Scene's error handling (a frame listener, `onTick`,
|
||||
a resize) stops the loop without a `renderError`. The notice wraps the
|
||||
widget's `render()` and `resize()` and raises it as one, once even with
|
||||
`rethrowRenderErrors`.
|
||||
- A throwing listener leaves its Cesium event mid-raise, with listener changes
|
||||
only queued. The wrapper finishes those raises first (`finishRaise()` over
|
||||
`frameEvents()`), so a layer turned off or on meanwhile behaves.
|
||||
- A render error marks the loop stopped while its next frame is still queued.
|
||||
The wrapper keeps the loop marked running, so a Retry or a shown tab
|
||||
(`src/app/tools.js`) before that frame starts no second loop.
|
||||
- After a lost context, later render errors are only logged, so the notice
|
||||
keeps saying why. The listener neither prevents nor restores the loss.
|
||||
- When the globe draws again, focus goes back where it was, or to the globe's
|
||||
canvas, which the map's keys read as the map; the canvas is focusable only
|
||||
until focus leaves or the globe is pressed. Focus the user moved stays put.
|
||||
- The notice sits above floating panels such as the Québec camera viewer, and
|
||||
its buttons are 44 px tall on touch screens.
|
||||
|
||||
An inline conversation panel never reloads or navigates its host's page: it
|
||||
offers Retry after a render error only, and nothing after a lost context, where
|
||||
the panel runtime's own notice applies. The panel's timer
|
||||
(`keepPanelRendering()`) draws only while Cesium's render loop is on, so it
|
||||
stops after a render error and draws again after Retry. An error a frame of
|
||||
the timer throws stops there: it has been raised as a render error, and
|
||||
thrown on it would only reach the host page.
|
||||
|
||||
A render error marks Cesium's loop stopped, but a frame drawn while the loop
|
||||
ran leaves its next animation frame queued: an error Scene catches itself
|
||||
returns to the loop, which queues the frame, and a frame the panel's timer
|
||||
draws leaves it queued. The `render()` and `resize()` wrapper keeps the loop
|
||||
marked running after such a call, so a Retry before that frame, or a tab shown
|
||||
again (`src/app/tools.js`), does not start a second loop that would draw every
|
||||
frame twice. The queued frame stops the loop itself if nothing restarted it.
|
||||
|
||||
The `webglcontextlost` listener does not `preventDefault()` the context loss or
|
||||
restore the context; it only reports it.
|
||||
the panel runtime's own notice applies. Its timer (`keepPanelRendering()`)
|
||||
draws only while the render loop is on. An error a timer frame throws is caught
|
||||
there: it has already been raised as a render error.
|
||||
|
||||
## Map Source control ownership
|
||||
|
||||
|
||||
+20
-35
@@ -4,14 +4,11 @@ import { describeError } from '../app/errors.js';
|
||||
import { appRootUrl } from '../share/shareLinkPath.js';
|
||||
|
||||
/**
|
||||
* Tell the user when the globe stops drawing, rather than leave a black globe
|
||||
* behind controls that still respond. Cesium stops its render loop after a
|
||||
* render error, and a page whose WebGL context was lost never draws again.
|
||||
*
|
||||
* Retry restarts the loop, and the notice stays until a frame renders without
|
||||
* an error; after a context loss Retry reloads the page. Reset opens the app's
|
||||
* root address. With `allowReload: false`, Reset is left out, and so is Retry
|
||||
* after a context loss. Returns a function that removes the notice.
|
||||
* Show a recoverable notice when the globe stops drawing, instead of a black
|
||||
* globe behind working controls. Retry restarts the render loop, or reloads
|
||||
* after a lost WebGL context; Reset opens the app's root address. With
|
||||
* `allowReload: false`, Reset is dropped, and so is Retry after a context loss.
|
||||
* Returns a function that removes the notice.
|
||||
*/
|
||||
export function installRenderFailureNotice(
|
||||
viewer,
|
||||
@@ -27,8 +24,8 @@ export function installRenderFailureNotice(
|
||||
const canvas = viewer.canvas ?? scene.canvas;
|
||||
let notice = null;
|
||||
let contextLost = false;
|
||||
// The error Scene raised as a render error and, with rethrowRenderErrors,
|
||||
// throws on: the render() wrapper must not raise it a second time.
|
||||
// With rethrowRenderErrors, Scene throws the error it has just raised;
|
||||
// the render() wrapper must not raise it again.
|
||||
let raisedByScene = null;
|
||||
let frameFailed = false;
|
||||
let removeFrameListeners = null;
|
||||
@@ -104,14 +101,10 @@ export function installRenderFailureNotice(
|
||||
};
|
||||
|
||||
const removeRenderError = scene.renderError.addEventListener(onRenderError);
|
||||
// An error thrown outside Scene's own error handling (a preRender or
|
||||
// postRender listener, the clock's onTick, a resize) also stops Cesium's
|
||||
// render loop, but raises no renderError, and Cesium reports it only in the
|
||||
// panel the app turns off. Raise it as a render error, so Cesium's handler,
|
||||
// an inline panel's timer and this notice all see it. Such an error passes
|
||||
// here before anything else runs, so finish the raises of frameEvents() it
|
||||
// cut short first: listeners added or removed while the loop is stopped
|
||||
// then apply at once, in order.
|
||||
// An error thrown outside Scene's error handling (a frame listener, onTick,
|
||||
// resize) stops the loop without a renderError. Raise one, so Cesium, an
|
||||
// inline panel's timer and this notice all see it. First finish the event
|
||||
// raises it cut short, so listeners added or removed meanwhile apply.
|
||||
const unguard = ['resize', 'render'].map((name) =>
|
||||
reportThrown(viewer.cesiumWidget, name, (error) => {
|
||||
frameEvents(viewer).forEach(finishRaise);
|
||||
@@ -130,11 +123,8 @@ export function installRenderFailureNotice(
|
||||
}
|
||||
|
||||
/**
|
||||
* The events Cesium raises in a frame outside Scene's own error handling, so
|
||||
* that a listener's error cuts their raise short: Scene's frame events, the
|
||||
* camera's and the clock's, and those raised after the frame renders, the
|
||||
* globe's tile load progress and the load events of the scene's tilesets.
|
||||
* Tilesets nested in a collection are not reached.
|
||||
* Events Cesium raises in a frame outside Scene's error handling, which a
|
||||
* listener's error can leave mid-raise. Only top-level tilesets are reached.
|
||||
*/
|
||||
function frameEvents(viewer) {
|
||||
const { scene } = viewer;
|
||||
@@ -164,10 +154,8 @@ function frameEvents(viewer) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish the raise a throwing listener cut short. Cesium's `Event.raiseEvent`
|
||||
* marks the event as raising until every listener returns, and until then it
|
||||
* only queues the listeners added or removed. Apply the queue as the raise
|
||||
* would have, so listeners added or removed from here on take effect at once.
|
||||
* Finish a raise a throwing listener cut short. Until a raise ends, Cesium's
|
||||
* `Event` only queues added and removed listeners; apply that queue.
|
||||
*/
|
||||
function finishRaise(event) {
|
||||
if (!event?._invokingListeners) return;
|
||||
@@ -187,15 +175,12 @@ function finishRaise(event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Report an error the widget's `name` method throws, and throw it on.
|
||||
* Report an error the widget's `name` method throws, and rethrow it.
|
||||
*
|
||||
* A render error marks Cesium's render loop stopped, but a call made while
|
||||
* the loop ran leaves the loop's next animation frame queued: the loop queues
|
||||
* it after a call that returns, and a frame an inline panel's timer draws
|
||||
* leaves it queued. That frame stops the loop itself, and a Retry or a shown
|
||||
* tab that comes first must not start a second loop beside it, so the loop
|
||||
* stays marked running. A call from the loop that throws is still marked
|
||||
* stopped by the loop's own catch.
|
||||
* A render error marks the loop stopped, but a call made while it ran leaves
|
||||
* its next frame queued, and that frame stops it. Keep the loop marked
|
||||
* running, so a Retry or a shown tab before that frame starts no second loop.
|
||||
* After a throw, the loop's own catch still marks it stopped.
|
||||
*/
|
||||
function reportThrown(widget, name, report) {
|
||||
const original = widget?.[name];
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Retry after an error thrown by a scene event listener. Cesium's
|
||||
// Event.raiseEvent has no try/finally: a throwing listener leaves the event
|
||||
// with _invokingListeners = true, so listeners added or removed afterwards are
|
||||
// only queued (_toAdd/_toRemove) until the next raise of that event completes.
|
||||
// Drives Cesium's real Event and startRenderLoop (CesiumWidget.prototype).
|
||||
// Retry after a scene event listener throws. Cesium's Event.raiseEvent has no
|
||||
// try/finally, so the event stays mid-raise and only queues listener changes.
|
||||
// Drives Cesium's real Event and startRenderLoop.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as Cesium from 'cesium';
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// Errors thrown in the render loop outside Scene's tryAndCatchError (a
|
||||
// preRender/postRender listener, clock.onTick, resize). Drives Cesium's real
|
||||
// startRenderLoop through the CesiumWidget.prototype.useDefaultRenderLoop
|
||||
// setter.
|
||||
// Errors thrown in the render loop outside Scene's tryAndCatchError. Drives
|
||||
// Cesium's real startRenderLoop through the useDefaultRenderLoop setter.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as Cesium from 'cesium';
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Retry after an error Scene catches itself (Scene.tryAndCatchError raises
|
||||
// renderError and returns). CesiumWidget's renderError handler marks the loop
|
||||
// stopped, but startRenderLoop still queues the loop's next animation frame.
|
||||
// A Retry that lands before that frame runs must not start a second loop.
|
||||
// Drives Cesium's real startRenderLoop and useDefaultRenderLoop setter
|
||||
// (CesiumWidget.prototype).
|
||||
// Retry after an error Scene catches itself: the loop is marked stopped but
|
||||
// its next frame is still queued, so an early Retry must not start a second
|
||||
// loop. Drives Cesium's real startRenderLoop and useDefaultRenderLoop setter.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as Cesium from 'cesium';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// src/ui/renderFailureView.test.mjs — the scene chrome renders the render
|
||||
// failure notice installRenderFailureNotice() publishes, with its actions.
|
||||
// The scene chrome renders the render failure notice and its actions.
|
||||
import '../testSupport/registerDom.mjs';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
@@ -5,8 +5,7 @@ import { msg, t } from '../../../i18n/index.js';
|
||||
import { useStatusStore } from '../../stores/status.js';
|
||||
import UiButton from '../../kit/UiButton.vue';
|
||||
|
||||
// The notice installRenderFailureNotice() (src/ui/renderFailure.js) publishes
|
||||
// while the globe cannot draw, with the actions it offers.
|
||||
// Published by installRenderFailureNotice() (src/ui/renderFailure.js).
|
||||
const { renderFailure: failure } = storeToRefs(useStatusStore());
|
||||
const panel = ref(null);
|
||||
const MESSAGES = {
|
||||
@@ -17,10 +16,8 @@ const MESSAGES = {
|
||||
'Rendering stopped after an error. Layers and controls still work, but the map is not updating.',
|
||||
),
|
||||
};
|
||||
// Focus moves to the first action each time the notice appears, so a
|
||||
// keyboard user can act on it at once. When the globe draws again, it goes
|
||||
// back where it was, or to the map if that cannot take it, unless the user
|
||||
// has moved it since.
|
||||
// Focus goes to the first action when the notice appears. When the globe
|
||||
// draws again, it goes back, or to the map, unless the user moved it.
|
||||
let focusBefore;
|
||||
watch(
|
||||
() => Boolean(failure.value),
|
||||
@@ -39,7 +36,7 @@ watch(
|
||||
if (active && active !== document.body) return;
|
||||
if (previous?.isConnected && previous !== document.body) {
|
||||
previous.focus({ preventScroll: true });
|
||||
// A control disabled, hidden or made inert since does not take it.
|
||||
// A control disabled, hidden or made inert meanwhile does not take it.
|
||||
if (document.activeElement === previous) return;
|
||||
}
|
||||
focusMap();
|
||||
@@ -47,10 +44,9 @@ watch(
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
// The globe's canvas, which the map's keys (Enter to finish a drawing, Space
|
||||
// to talk) read as the map. It takes focus only this once: when focus moves
|
||||
// on, or the globe is pressed, it goes back to a canvas that cannot take
|
||||
// focus, and a press leaves focus on the page, as Cesium has it.
|
||||
// The map's keys (Enter to finish a drawing, Space to talk) read the canvas
|
||||
// as the map. It stays focusable only until focus leaves or the globe is
|
||||
// pressed; a press leaves focus on the page, as in Cesium.
|
||||
function focusMap() {
|
||||
const canvas = document.querySelector(
|
||||
'#cesiumContainer .cesium-widget canvas',
|
||||
|
||||
Reference in new issue
Block a user