A great deal of the complexity of the current code is that we must chain 2 reactive values (one in the map "GameScene.MapPlayersByKey" and one in the snapshot store). The new generic MapStore class can be used to listen to stores inside a map. When the store inside the map, or the map itself is modified, the resulting store is updated.
122 lines
3.9 KiB
TypeScript
122 lines
3.9 KiB
TypeScript
import type { Readable, Subscriber, Unsubscriber, Writable } from "svelte/store";
|
|
import { get, readable, writable } from "svelte/store";
|
|
|
|
/**
|
|
* Is it a Map? Is it a Store? No! It's a MapStore!
|
|
*
|
|
* The MapStore behaves just like a regular JS Map, but... it is also a regular Svelte store.
|
|
*
|
|
* As a bonus, you can also get a store on any given key of the map.
|
|
*
|
|
* For instance:
|
|
*
|
|
* const mapStore = new MapStore<string, string>();
|
|
* mapStore.getStore('foo').subscribe((value) => {
|
|
* console.log('Foo key has been written to the store. New value: ', value);
|
|
* });
|
|
* mapStore.set('foo', 'bar');
|
|
*
|
|
*
|
|
* Even better, if the items stored in map contain stores, you can directly get the store to those values:
|
|
*
|
|
* const mapStore = new MapStore<string, {
|
|
* nestedStore: Readable<string>
|
|
* }>();
|
|
*
|
|
* mapStore.getNestedStore('foo', item => item.nestedStore).subscribe((value) => {
|
|
* console.log('Foo key has been written to the store or the nested store has been updated. New value: ', value);
|
|
* });
|
|
* mapStore.set('foo', {
|
|
* nestedStore: writable('bar')
|
|
* });
|
|
* // Whenever the nested store is updated OR the 'foo' key is overwritten, the store returned by mapStore.getNestedStore
|
|
* // will be triggered.
|
|
*/
|
|
export class MapStore<K, V> extends Map<K, V> implements Readable<Map<K, V>> {
|
|
private readonly store = writable(this);
|
|
private readonly storesByKey = new Map<K, Writable<V | undefined>>();
|
|
|
|
subscribe(run: Subscriber<Map<K, V>>, invalidate?: (value?: Map<K, V>) => void): Unsubscriber {
|
|
return this.store.subscribe(run, invalidate);
|
|
}
|
|
|
|
clear() {
|
|
super.clear();
|
|
this.store.set(this);
|
|
this.storesByKey.forEach((store) => {
|
|
store.set(undefined);
|
|
});
|
|
}
|
|
|
|
delete(key: K): boolean {
|
|
const result = super.delete(key);
|
|
if (result) {
|
|
this.store.set(this);
|
|
this.storesByKey.get(key)?.set(undefined);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
set(key: K, value: V): this {
|
|
super.set(key, value);
|
|
this.store.set(this);
|
|
this.storesByKey.get(key)?.set(value);
|
|
return this;
|
|
}
|
|
|
|
getStore(key: K): Readable<V | undefined> {
|
|
const store = writable(this.get(key), () => {
|
|
return () => {
|
|
// No more subscribers!
|
|
this.storesByKey.delete(key);
|
|
};
|
|
});
|
|
this.storesByKey.set(key, store);
|
|
return store;
|
|
}
|
|
|
|
/**
|
|
* Returns an "inner" store inside a value stored in the map.
|
|
*/
|
|
getNestedStore<T>(key: K, accessor: (value: V) => Readable<T> | undefined): Readable<T | undefined> {
|
|
const initVal = this.get(key);
|
|
let initStore: Readable<T> | undefined;
|
|
let initStoreValue: T | undefined;
|
|
if (initVal) {
|
|
initStore = accessor(initVal);
|
|
if (initStore !== undefined) {
|
|
initStoreValue = get(initStore);
|
|
}
|
|
}
|
|
|
|
return readable(initStoreValue, (set) => {
|
|
const storeByKey = this.getStore(key);
|
|
|
|
let unsubscribeDeepStore: Unsubscriber | undefined;
|
|
const unsubscribe = storeByKey.subscribe((newMapValue) => {
|
|
if (unsubscribeDeepStore) {
|
|
unsubscribeDeepStore();
|
|
}
|
|
if (newMapValue === undefined) {
|
|
set(undefined);
|
|
} else {
|
|
const deepValueStore = accessor(newMapValue);
|
|
if (deepValueStore !== undefined) {
|
|
set(get(deepValueStore));
|
|
|
|
unsubscribeDeepStore = deepValueStore.subscribe((value) => {
|
|
set(value);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return () => {
|
|
unsubscribe();
|
|
if (unsubscribeDeepStore) {
|
|
unsubscribeDeepStore();
|
|
}
|
|
};
|
|
});
|
|
}
|
|
}
|