Skip to content

Commit 953a070

Browse files
authored
Merge pull request #66 from alan-wu/context-restore
Add a way to handle GL context lost event.
2 parents a1c34b7 + 4572726 commit 953a070

4 files changed

Lines changed: 138 additions & 14 deletions

File tree

src/flatmap.ts

Lines changed: 106 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ export class FlatMap
216216
#bounds: maplibregl.LngLatBounds
217217
#callbacks: FlatMapCallback[] = []
218218
#container: string
219+
#contextLost = false
219220
#created: string
220221
#datasetToFeatureIds: FeatureIdMap = new Map()
221222
#details: FlatMapIndex
@@ -230,13 +231,15 @@ export class FlatMap
230231
#mapServer: FlatMapServer
231232
#mapSourceToFeatureIds: FeatureIdMap = new Map()
232233
#mapTermGraph: MapTermGraph
234+
#mapTermGraphLoaded = false
233235
#modelToFeatureIds: FeatureIdMap = new Map()
234236
#normalisedOrigin: [number, number]
235237
#normalised_size: [number, number]
236238
#options: MapDescriptionOptions
237239
#pathways: FlatMapPathways
238240
#searchIndex: SearchIndex = new SearchIndex()
239241
#startupState = -1
242+
#style: FlatMapStyleSpecification
240243
#taxon: string|null
241244
#taxonNames = new Map()
242245
#taxonToFeatureIds: FeatureIdMap = new Map()
@@ -259,6 +262,7 @@ export class FlatMap
259262
this.#layers = mapDescription.layers
260263
this.#options = mapDescription.options
261264
this.#pathways = mapDescription.pathways
265+
this.#style = mapDescription.style
262266
this.#mapTermGraph = new MapTermGraph()
263267

264268
const sckanProvenance = mapDescription.details.connectivity
@@ -310,19 +314,75 @@ export class FlatMap
310314
}
311315
}
312316

317+
this.#initialiseMap()
318+
}
319+
320+
321+
/**
322+
* Force the maplibregl instance to lose it's GL conontext
323+
*
324+
*/
325+
forceContextLoss()
326+
//=======================================
327+
{
328+
this.#contextLostCallback()
329+
}
330+
331+
#contextLostCallback(event = undefined)
332+
//=======================================
333+
{
334+
// Clean up some of the resources after the GL context is lost
335+
336+
if (!this.#contextLost) {
337+
this.closeMinimap()
338+
this.#userInteractions.freeLayersResource()
339+
const canvas = this.#map.getCanvas()
340+
canvas.removeEventListener('webglcontextlost', this.#contextLostCallback, false);
341+
this.#map.remove()
342+
this.#contextLost = true
343+
this.#startupState = -1
344+
this.callback('context-lost', undefined)
345+
346+
// Prevent the browser's default behavior
347+
348+
if (event) {
349+
event.preventDefault()
350+
}
351+
}
352+
}
353+
354+
/**
355+
* Restore a maplibregl instance after the cotnext is lost
356+
*
357+
*/
358+
forceContextRestore()
359+
//=======================================
360+
{
361+
if (this.#contextLost) {
362+
this.#initialiseMap()
363+
}
364+
}
365+
366+
/**
367+
* Initialise a maplibregl map instance
368+
*
369+
*/
370+
#initialiseMap()
371+
//=======================================
372+
{
313373
// Set options for the map
314374

315375
const mapOptions: maplibregl.MapOptions = {
316-
style: mapDescription.style,
317-
container: container,
376+
style: this.#style,
377+
container: this.#container,
318378
attributionControl: false
319379
}
320380

321-
if ('maxZoom' in mapDescription.options) {
322-
mapOptions.maxZoom = mapDescription.options.maxZoom! - 0.001
381+
if ('maxZoom' in this.#options) {
382+
mapOptions.maxZoom = this.#options.maxZoom! - 0.001
323383
}
324-
if ('minZoom' in mapDescription.options) {
325-
mapOptions.minZoom = mapDescription.options.minZoom
384+
if ('minZoom' in this.#options) {
385+
mapOptions.minZoom = this.#options.minZoom
326386
}
327387

328388
// Only show location in address bar when debugging
@@ -331,17 +391,25 @@ export class FlatMap
331391

332392
// Set bounds if it is set in the map's options
333393

334-
if ('bounds' in mapDescription.options) {
335-
mapOptions.bounds = mapDescription.options.bounds
394+
if ('bounds' in this.#options) {
395+
mapOptions.bounds = this.#options.bounds
336396
}
337397

338398
// Create the map
339399

340400
this.#map = new maplibregl.Map(mapOptions)
341401

402+
403+
// Get the map's canvas element
404+
const canvas = this.#map.getCanvas()
405+
406+
// Listen for the 'contextlost' event
407+
canvas.addEventListener('webglcontextlost', this.#contextLostCallback.bind(this), false);
408+
409+
342410
// Show extra information if debugging
343411

344-
if (mapDescription.options.debug === true) {
412+
if (this.#options.debug === true) {
345413
this.#map.showTileBoundaries = true
346414
this.#map.showCollisionBoxes = true
347415
}
@@ -359,6 +427,7 @@ export class FlatMap
359427
// and map has rendered
360428

361429
const idleSubscription = this.#map.on('idle', async() => {
430+
362431
if (this.#startupState === -1) {
363432
this.#startupState = 0
364433
await this.#setupUserInteractions()
@@ -388,7 +457,16 @@ export class FlatMap
388457
this.#map!.fitBounds(this.#bounds, {animate: false})
389458
this.#startupState = 3
390459

460+
461+
// Trigger a context-restored callback
462+
463+
if (this.#contextLost) {
464+
this.callback('context-restored', undefined)
465+
this.#contextLost = false
466+
}
467+
391468
idleSubscription.unsubscribe()
469+
392470
}
393471
})
394472
}
@@ -418,9 +496,13 @@ export class FlatMap
418496
// Load icons used for clustered markers
419497
await loadMarkerIcons(this.#map!)
420498

421-
// Load anatomical term hierarchy for the flatmap
422-
const termGraph = (await this.#mapServer.mapTermGraph(this.#uuid))!
423-
this.#mapTermGraph.load(termGraph)
499+
// Load anatomical term hierarchy for the flatmap, this is not required
500+
// again after it has been loaded once
501+
if (!this.#mapTermGraphLoaded) {
502+
const termGraph = (await this.#mapServer.mapTermGraph(this.#uuid))!
503+
this.#mapTermGraph.load(termGraph)
504+
this.#mapTermGraphLoaded = true
505+
}
424506

425507
// Layers have now loaded so finish setting up
426508
this.#userInteractions = new UserInteractions(this)
@@ -1036,6 +1118,18 @@ export class FlatMap
10361118
}
10371119
}
10381120

1121+
/**
1122+
* A flag indicating whether gl context is lost
1123+
* @type {Boolean}
1124+
*
1125+
* @group Properties
1126+
*/
1127+
get contextLost(): Boolean
1128+
//===================================
1129+
{
1130+
return this.#contextLost
1131+
}
1132+
10391133
/**
10401134
* @group Properties
10411135
*/

src/interactions.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,17 @@ export class UserInteractions
559559
this.#layerManager.activate(layerId, enable)
560560
}
561561

562+
/**
563+
* Free up gl context used in layermanager, only call this when
564+
* the GL context is lost
565+
*
566+
*/
567+
freeLayersResource()
568+
//=======================================
569+
{
570+
this.#layerManager.freeDeckGLResource()
571+
}
572+
562573
enableFlightPaths(enable=true)
563574
//============================
564575
{

src/layers/deckgl.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@ import {FlatMap} from '../flatmap'
3131

3232
export class DeckGlOverlay
3333
{
34-
#overlay: MapboxOverlay = new MapboxOverlay({layers: []})
34+
#overlay: MapboxOverlay = new MapboxOverlay(
35+
{
36+
interleaved: true,
37+
layers: [],
38+
getCursor: () => 'default'
39+
}
40+
)
3541
#layers: Map<string, Layer> = new Map()
3642

3743
#map: MapLibreMap
@@ -50,12 +56,17 @@ export class DeckGlOverlay
5056
this.#setLayers()
5157
}
5258

59+
finalise()
60+
//====================
61+
{
62+
this.#overlay.finalize()
63+
}
64+
5365
queryFeaturesAtPoint(point)
5466
//=========================
5567
{
5668
return this.#overlay
5769
.pickMultipleObjects(point)
58-
return []
5970
}
6071

6172
removeLayer(layerId: string)

src/layers/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,14 @@ export class LayerManager
730730
}
731731
}
732732

733+
freeDeckGLResource()
734+
//=======================================
735+
{
736+
if (this.#deckGlOverlay) {
737+
this.#deckGlOverlay.finalise()
738+
}
739+
}
740+
733741
enableSckanPaths(_sckanState, _enable=true)
734742
//=======================================
735743
{

0 commit comments

Comments
 (0)