Skip to content

Commit

Permalink
Show a message when the graphics system has a context loss
Browse files Browse the repository at this point in the history
(closes #1658)

This shows a message in the red status bar like:
"The graphics system has encountered a problem.  Rapid will try to continue with reduced quality."

This changes the GraphicsSystem `contextlost`/`contextrestored` events to a more generic
`statuschange` event, this is more what other parts of Rapid like the UiStatus bar need.
  • Loading branch information
bhousel committed Jan 13, 2025
1 parent 2668e7d commit c96ea51
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 18 deletions.
4 changes: 3 additions & 1 deletion data/core.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -559,13 +559,15 @@ en:
hidden_warning:
one: "{count} hidden feature"
other: "{count} hidden features"
osm_api_status:
status:
message:
error: Unable to reach the OpenStreetMap API. Your edits are safe locally. Check your network connection.
offline: The OpenStreetMap API is offline. Your edits are safe locally. Please come back later.
readonly: The OpenStreetMap API is currently read-only. You can continue editing, but must wait to save your changes.
ratelimit: You have downloaded too much data from OpenStreetMap. Please try again in {seconds} seconds or switch accounts.
local_storage_full: You have made too many edits to back up. Consider saving your changes now.
contextlost: The graphics system has encountered a problem. Rapid will try to continue with reduced quality.
dismiss: Dismiss
retry: Retry

commit:
Expand Down
6 changes: 4 additions & 2 deletions data/l10n/core.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -742,14 +742,16 @@
"other": "{count} hidden features"
}
},
"osm_api_status": {
"status": {
"message": {
"error": "Unable to reach the OpenStreetMap API. Your edits are safe locally. Check your network connection.",
"offline": "The OpenStreetMap API is offline. Your edits are safe locally. Please come back later.",
"readonly": "The OpenStreetMap API is currently read-only. You can continue editing, but must wait to save your changes.",
"ratelimit": "You have downloaded too much data from OpenStreetMap. Please try again in {seconds} seconds or switch accounts.",
"local_storage_full": "You have made too many edits to back up. Consider saving your changes now."
"local_storage_full": "You have made too many edits to back up. Consider saving your changes now.",
"contextlost": "The graphics system has encountered a problem. Rapid will try to continue with reduced quality."
},
"dismiss": "Dismiss",
"retry": "Retry"
},
"commit": {
Expand Down
11 changes: 6 additions & 5 deletions modules/core/GraphicsSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ const THROTTLE = 250; // throttled rendering milliseconds (for now)
* `textures` PixiTextures manages the textures
*
* Events available:
* `draw` Fires after a full redraw
* `move` Fires after the map's transform has changed (can fire frequently)
* ('move' is mostly for when you want to update some content that floats over the map)
* `contextchange` Fires after the WebGLContext has changed (to let listeners know Pixi has been replaced)
* `draw` Fires after a full redraw
* `move` Fires after the map's transform has changed (can fire frequently)
* ('move' is mostly for when you want to update some content that floats over the map)
* `statuschange` Fires on status changes, receives 'contextlost' or 'contextrestored'
*/
export class GraphicsSystem extends AbstractSystem {

Expand Down Expand Up @@ -805,6 +805,7 @@ export class GraphicsSystem extends AbstractSystem {
// We may be able to handle this better eventually, but for now we will just
// assume the whole graphics system is getting thrown out.
this.context.enter('browse');
this.emit('statuschange', 'contextlost');

// Normally Pixi's `GLContextSystem` would try to restore context if we call `render()`
// see https://pixijs.download/release/docs/rendering.GlContextSystem.html
Expand Down Expand Up @@ -845,7 +846,7 @@ export class GraphicsSystem extends AbstractSystem {
this.events.enable(); // resume listening
this.resume(); // resume rendering
this.ticker.start(); // resume ticking
this.emit('contextchange');
this.emit('statuschange', 'contextrestored');
});
}

Expand Down
44 changes: 37 additions & 7 deletions modules/ui/UiApiStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class UiApiStatus {

this._apiStatus = null;
this._backupStatus = null;
this._gfxStatus = null;

// D3 selections
this.$parent = null;
Expand All @@ -28,10 +29,13 @@ export class UiApiStatus {
this.render = this.render.bind(this);
this._onApiStatusChange = this._onApiStatusChange.bind(this);
this._onBackupStatusChange = this._onBackupStatusChange.bind(this);
this._onGfxStatusChange = this._onGfxStatusChange.bind(this);

// Setup event listeners
const editor = context.systems.editor;
const gfx = context.systems.gfx;
editor.on('backupstatuschange', this._onBackupStatusChange);
gfx.on('statuschange', this._onGfxStatusChange);

// Note that it's possible to run in an environment without OSM.
const osm = context.services.osm;
Expand Down Expand Up @@ -91,14 +95,27 @@ export class UiApiStatus {
// Empty out the DOM content and rebuild from scratch..
$apiStatus.html('');

if (this._apiStatus === 'readonly') {
$apiStatus.text(l10n.t('osm_api_status.message.readonly'));
if (this._gfxStatus === 'contextlost') {
$apiStatus.text(l10n.t('status.message.contextlost') + ' ');

$apiStatus
.append('a')
.attr('href', '#')
.text(l10n.t('status.dismiss'))
.on('click.dismiss', e => {
e.preventDefault();
this._gfxStatus = null;
this.render();
});

} else if (this._apiStatus === 'readonly') {
$apiStatus.text(l10n.t('status.message.readonly'));

} else if (this._apiStatus === 'offline') {
$apiStatus.text(l10n.t('osm_api_status.message.offline'));
$apiStatus.text(l10n.t('status.message.offline'));

} else if (this._apiStatus === 'ratelimit' && rateLimit) {
$apiStatus.text(l10n.t('osm_api_status.message.ratelimit', { seconds: rateLimit.remaining }));
$apiStatus.text(l10n.t('status.message.ratelimit', { seconds: rateLimit.remaining }));

if (osm && !osm.authenticated()) { // Tell the user to log in..
$apiStatus
Expand All @@ -116,21 +133,21 @@ export class UiApiStatus {
}

} else if (this._apiStatus === 'error') { // Some other problem, "check your network connection"..
$apiStatus.text(l10n.t('osm_api_status.message.error') + ' ');
$apiStatus.text(l10n.t('status.message.error') + ' ');

if (osm) { // Let the user manually retry their connection
$apiStatus
.append('a')
.attr('href', '#')
.text(l10n.t('osm_api_status.retry'))
.text(l10n.t('status.retry'))
.on('click.retry', e => {
e.preventDefault();
osm.throttledReloadApiStatus();
});
}

} else if (this._backupStatus === 'error') { // API is fine, but backups are not..
$apiStatus.text(l10n.t('osm_api_status.message.local_storage_full'));
$apiStatus.text(l10n.t('status.message.local_storage_full'));
}
}

Expand All @@ -156,4 +173,17 @@ export class UiApiStatus {
this._backupStatus = wasSuccessful ? 'ok' : 'error';
this.render();
}


/**
* _onGfxStatusChange
* Callback function called when the GraphicsSystem loses context
* @param {string} status - one of 'contextlost' or 'contextrestored'
*/
_onGfxStatusChange(status) {
if (status === 'contextlost') { // notify the user about this
this._gfxStatus = status;
this.render();
}
}
}
18 changes: 15 additions & 3 deletions modules/ui/UiMinimap.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class UiMinimap {
this.toggle = this.toggle.bind(this);
this._setupKeybinding = this._setupKeybinding.bind(this);
this._draw = this._draw.bind(this);
this._resetAsync = this._resetAsync.bind(this);
this._onGfxStatusChange = this._onGfxStatusChange.bind(this);
this._update = this._update.bind(this);
this._zoomStarted = this._zoomStarted.bind(this);
this._zoomed = this._zoomed.bind(this);
Expand Down Expand Up @@ -447,7 +447,7 @@ renderer.view.canvas = mainCanvas; // restore main canvas

// event handlers
gfx.on('draw', this._update);
gfx.on('contextchange', this._resetAsync);
gfx.on('statuschange', this._onGfxStatusChange);

// Mock Stage
const stage = new PIXI.Container();
Expand Down Expand Up @@ -494,6 +494,18 @@ renderer.view.canvas = mainCanvas; // restore main canvas
}


/**
* _onGfxStatusChange
* Callback function called when the GraphicsSystem loses/restores context
* @param {string} status - one of 'contextlost' or 'contextrestored'
*/
_onGfxStatusChange(status) {
if (status === 'contextrestored') {
this._resetAsync();
}
}


/**
* _resetAsync
* Replace the Minimap after a context loss
Expand All @@ -505,7 +517,7 @@ renderer.view.canvas = mainCanvas; // restore main canvas

// event handlers
gfx.off('draw', this._update);
gfx.off('contextchange', this._resetAsync);
gfx.off('statuschange', this._onGfxStatusChange);

if (this.layer) {
this.layer.destroyAll();
Expand Down

0 comments on commit c96ea51

Please sign in to comment.