mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
fix(maps): stop all delivery once the controller is destroyed
A listener could destroy the controller and re-subscribe one still pending in the snapshot, which then ran. Delivery now stops at destruction and a destroyed controller takes no listeners. Adds ordering tests for adds, duplicates and re-adds during delivery, and moves the current-state note after the section intro.
This commit is contained in:
1 parent
69a84c47a1
commit
b9fa0eedcc
3 files changed
+41
-3
No files matched your search
@@ -5209,8 +5209,6 @@ the project's MIT license; see `DATA_SOURCES.md`.
|
|||||||
|
|
||||||
## Map source factories and coordination
|
## Map source factories and coordination
|
||||||
|
|
||||||
A subscriber removed, or a controller destroyed, during a settled-state delivery receives no further callbacks from that delivery.
|
|
||||||
|
|
||||||
Map selection is composed from separate imagery, terrain and 3D factories.
|
Map selection is composed from separate imagery, terrain and 3D factories.
|
||||||
The default registry retains Google 3D, Bing Aerial/Labels, Esri Satellite and
|
The default registry retains Google 3D, Bing Aerial/Labels, Esri Satellite and
|
||||||
OSM, including their setup guidance and existing preset/share IDs. Esri still
|
OSM, including their setup guidance and existing preset/share IDs. Esri still
|
||||||
@@ -5218,6 +5216,10 @@ falls back to OSM on construction failure or two active tile failures, and
|
|||||||
credits follow the source actually displayed. Terrain remains lazy while the
|
credits follow the source actually displayed. Terrain remains lazy while the
|
||||||
photoreal globe is hidden.
|
photoreal globe is hidden.
|
||||||
|
|
||||||
|
Map-stack subscribers hear each settled activation once. A subscriber removed
|
||||||
|
during a delivery, or every subscriber once the controller is destroyed, gets
|
||||||
|
no further callback from it, and a destroyed controller accepts no new ones.
|
||||||
|
|
||||||
Map credentials are passed to each source constructor rather than changing SDK-wide Google or ion defaults.
|
Map credentials are passed to each source constructor rather than changing SDK-wide Google or ion defaults.
|
||||||
|
|
||||||
Bing Aerial and Bing Labels resolve their ion imagery endpoint with the supplied
|
Bing Aerial and Bing Labels resolve their ion imagery endpoint with the supplied
|
||||||
|
|||||||
@@ -131,7 +131,8 @@ export class MapSourceController {
|
|||||||
* @returns {() => void} Unsubscribe.
|
* @returns {() => void} Unsubscribe.
|
||||||
*/
|
*/
|
||||||
subscribe(listener) {
|
subscribe(listener) {
|
||||||
if (typeof listener !== 'function') return () => {};
|
// A destroyed controller never settles again, so it takes no listeners.
|
||||||
|
if (typeof listener !== 'function' || this._destroyed) return () => {};
|
||||||
this._subscribers.add(listener);
|
this._subscribers.add(listener);
|
||||||
return () => {
|
return () => {
|
||||||
this._subscribers.delete(listener);
|
this._subscribers.delete(listener);
|
||||||
@@ -142,6 +143,7 @@ export class MapSourceController {
|
|||||||
if (this._destroyed || !this._subscribers.size) return;
|
if (this._destroyed || !this._subscribers.size) return;
|
||||||
const state = this.getState();
|
const state = this.getState();
|
||||||
for (const listener of [...this._subscribers]) {
|
for (const listener of [...this._subscribers]) {
|
||||||
|
if (this._destroyed) return;
|
||||||
if (!this._subscribers.has(listener)) continue;
|
if (!this._subscribers.has(listener)) continue;
|
||||||
try {
|
try {
|
||||||
listener(state);
|
listener(state);
|
||||||
|
|||||||
@@ -53,3 +53,37 @@ test('destroying the controller during delivery stops remaining listeners', asyn
|
|||||||
await controller.setStack('fixture');
|
await controller.setStack('fixture');
|
||||||
assert.deepEqual(calls, ['destroy']);
|
assert.deepEqual(calls, ['destroy']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a listener re-added after destroy during delivery is not called', async () => {
|
||||||
|
const controller = fixture();
|
||||||
|
const calls = [];
|
||||||
|
const later = () => calls.push('later');
|
||||||
|
controller.subscribe(() => {
|
||||||
|
calls.push('destroy');
|
||||||
|
controller.destroy();
|
||||||
|
controller.subscribe(later);
|
||||||
|
});
|
||||||
|
controller.subscribe(later);
|
||||||
|
await controller.setStack('fixture');
|
||||||
|
assert.deepEqual(calls, ['destroy']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('delivery order: added during delivery waits, duplicates run once, re-added runs', async () => {
|
||||||
|
const controller = fixture();
|
||||||
|
const calls = [];
|
||||||
|
const twice = () => calls.push('twice');
|
||||||
|
const readded = () => calls.push('readded');
|
||||||
|
let offReadded;
|
||||||
|
controller.subscribe(() => {
|
||||||
|
calls.push('first');
|
||||||
|
controller.subscribe(() => calls.push('added-late'));
|
||||||
|
offReadded();
|
||||||
|
controller.subscribe(readded);
|
||||||
|
});
|
||||||
|
controller.subscribe(twice);
|
||||||
|
controller.subscribe(twice);
|
||||||
|
offReadded = controller.subscribe(readded);
|
||||||
|
await controller.setStack('fixture');
|
||||||
|
assert.deepEqual(calls, ['first', 'twice', 'readded']);
|
||||||
|
controller.destroy();
|
||||||
|
});
|
||||||
Reference in new issue
Block a user