forked from MapComplete/MapComplete
		
	Refactoring: improve caching
This commit is contained in:
		
							parent
							
								
									f203a1158d
								
							
						
					
					
						commit
						e36e9123f3
					
				
					 5 changed files with 91 additions and 20 deletions
				
			
		|  | @ -2,8 +2,50 @@ import { FeatureSource } from "../FeatureSource" | |||
| import { Feature } from "geojson" | ||||
| import TileLocalStorage from "./TileLocalStorage" | ||||
| import { GeoOperations } from "../../GeoOperations" | ||||
| import FeaturePropertiesStore from "./FeaturePropertiesStore" | ||||
| import { UIEventSource } from "../../UIEventSource" | ||||
| import { Utils } from "../../../Utils" | ||||
| 
 | ||||
| class SingleTileSaver { | ||||
|     private readonly _storage: UIEventSource<Feature[]> | ||||
|     private readonly _registeredIds = new Set<string>() | ||||
|     private readonly _featureProperties: FeaturePropertiesStore | ||||
|     private readonly _isDirty = new UIEventSource(false) | ||||
|     constructor( | ||||
|         storage: UIEventSource<Feature[]> & { flush: () => void }, | ||||
|         featureProperties: FeaturePropertiesStore | ||||
|     ) { | ||||
|         this._storage = storage | ||||
|         this._featureProperties = featureProperties | ||||
|         this._isDirty.stabilized(1000).addCallbackD((isDirty) => { | ||||
|             if (!isDirty) { | ||||
|                 return | ||||
|             } | ||||
|             // 'isDirty' is set when tags of some object have changed
 | ||||
|             storage.flush() | ||||
|             this._isDirty.setData(false) | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     public saveFeatures(features: Feature[]) { | ||||
|         if (Utils.sameList(features, this._storage.data)) { | ||||
|             return | ||||
|         } | ||||
|         for (const feature of features) { | ||||
|             const id = feature.properties.id | ||||
|             if (this._registeredIds.has(id)) { | ||||
|                 continue | ||||
|             } | ||||
|             this._featureProperties.getStore(id)?.addCallbackAndRunD(() => { | ||||
|                 this._isDirty.setData(true) | ||||
|             }) | ||||
|             this._registeredIds.add(id) | ||||
|         } | ||||
| 
 | ||||
|         this._storage.setData(features) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /*** | ||||
|  * Saves all the features that are passed in to localstorage, so they can be retrieved on the next run | ||||
|  * | ||||
|  | @ -12,16 +54,27 @@ import { Utils } from "../../../Utils" | |||
|  * Also see the sibling class | ||||
|  */ | ||||
| export default class SaveFeatureSourceToLocalStorage { | ||||
|     constructor(layername: string, zoomlevel: number, features: FeatureSource) { | ||||
|     constructor( | ||||
|         layername: string, | ||||
|         zoomlevel: number, | ||||
|         features: FeatureSource, | ||||
|         featureProperties: FeaturePropertiesStore | ||||
|     ) { | ||||
|         const storage = TileLocalStorage.construct<Feature[]>(layername) | ||||
|         const singleTileSavers: Map<number, SingleTileSaver> = new Map<number, SingleTileSaver>() | ||||
|         features.features.addCallbackAndRunD((features) => { | ||||
|             const sliced = GeoOperations.slice(zoomlevel, features) | ||||
| 
 | ||||
|             sliced.forEach((features, tileIndex) => { | ||||
|                 const src = storage.getTileSource(tileIndex) | ||||
|                 if (Utils.sameList(src.data, features)) { | ||||
|                     return | ||||
|                 let tileSaver = singleTileSavers.get(tileIndex) | ||||
|                 if (tileSaver === undefined) { | ||||
|                     const src = storage.getTileSource(tileIndex) | ||||
|                     tileSaver = new SingleTileSaver(src, featureProperties) | ||||
|                     singleTileSavers.set(tileIndex, tileSaver) | ||||
|                 } | ||||
|                 src.setData(features) | ||||
|                 // Don't cache not-uploaded features yet - they'll be cached when the receive their id
 | ||||
|                 features = features.filter((f) => !f.properties.id.match(/(node|way)\/-[0-9]+/)) | ||||
|                 tileSaver.saveFeatures(features) | ||||
|             }) | ||||
|         }) | ||||
|     } | ||||
|  |  | |||
|  | @ -4,12 +4,14 @@ import { UIEventSource } from "../../UIEventSource" | |||
| /** | ||||
|  * A class which allows to read/write a tile to local storage. | ||||
|  * | ||||
|  * Does the heavy lifting for LocalStorageFeatureSource and SaveFeatureToLocalStorage | ||||
|  * Does the heavy lifting for LocalStorageFeatureSource and SaveFeatureToLocalStorage. | ||||
|  * | ||||
|  * Note: OSM-features with a negative id are ignored | ||||
|  */ | ||||
| export default class TileLocalStorage<T> { | ||||
|     private static perLayer: Record<string, TileLocalStorage<any>> = {} | ||||
|     private readonly _layername: string | ||||
|     private readonly cachedSources: Record<number, UIEventSource<T>> = {} | ||||
|     private readonly cachedSources: Record<number, UIEventSource<T> & { flush: () => void }> = {} | ||||
| 
 | ||||
|     private constructor(layername: string) { | ||||
|         this._layername = layername | ||||
|  | @ -27,24 +29,26 @@ export default class TileLocalStorage<T> { | |||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Constructs a UIEventSource element which is synced with localStorage | ||||
|      * @param layername | ||||
|      * @param tileIndex | ||||
|      * Constructs a UIEventSource element which is synced with localStorage. | ||||
|      * Supports 'flush' | ||||
|      */ | ||||
|     public getTileSource(tileIndex: number): UIEventSource<T> { | ||||
|     public getTileSource(tileIndex: number): UIEventSource<T> & { flush: () => void } { | ||||
|         const cached = this.cachedSources[tileIndex] | ||||
|         if (cached) { | ||||
|             return cached | ||||
|         } | ||||
|         const src = UIEventSource.FromPromise(this.GetIdb(tileIndex)) | ||||
|         const src = <UIEventSource<T> & { flush: () => void }>( | ||||
|             UIEventSource.FromPromise(this.GetIdb(tileIndex)) | ||||
|         ) | ||||
|         src.flush = () => this.SetIdb(tileIndex, src.data) | ||||
|         src.addCallbackD((data) => this.SetIdb(tileIndex, data)) | ||||
|         this.cachedSources[tileIndex] = src | ||||
|         return src | ||||
|     } | ||||
| 
 | ||||
|     private SetIdb(tileIndex: number, data): void { | ||||
|     private async SetIdb(tileIndex: number, data): Promise<void> { | ||||
|         try { | ||||
|             IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data) | ||||
|             await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data) | ||||
|         } catch (e) { | ||||
|             console.error( | ||||
|                 "Could not save tile to indexed-db: ", | ||||
|  | @ -52,7 +56,9 @@ export default class TileLocalStorage<T> { | |||
|                 "tileIndex is:", | ||||
|                 tileIndex, | ||||
|                 "for layer", | ||||
|                 this._layername | ||||
|                 this._layername, | ||||
|                 "data is", | ||||
|                 data | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -20,7 +20,14 @@ export default class LocalStorageFeatureSource extends DynamicTileSource { | |||
|         const storage = TileLocalStorage.construct<Feature[]>(layername) | ||||
|         super( | ||||
|             zoomlevel, | ||||
|             (tileIndex) => new StaticFeatureSource(storage.getTileSource(tileIndex)), | ||||
|             (tileIndex) => | ||||
|                 new StaticFeatureSource( | ||||
|                     storage | ||||
|                         .getTileSource(tileIndex) | ||||
|                         .map((features) => | ||||
|                             features?.filter((f) => !f.properties.id.match(/(node|way)\/-[0-9]+/)) | ||||
|                         ) | ||||
|                 ), | ||||
|             mapProperties, | ||||
|             options | ||||
|         ) | ||||
|  |  | |||
|  | @ -38,11 +38,11 @@ export class IdbLocalStorage { | |||
|         return src | ||||
|     } | ||||
| 
 | ||||
|     public static SetDirectly(key: string, value) { | ||||
|         idb.set(key, value) | ||||
|     public static SetDirectly(key: string, value): Promise<void> { | ||||
|         return idb.set(key, value) | ||||
|     } | ||||
| 
 | ||||
|     static GetDirectly(key: string) { | ||||
|     static GetDirectly(key: string): Promise<void> { | ||||
|         return idb.get(key) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -158,7 +158,12 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
|         this.perLayer = perLayer.perLayer | ||||
| 
 | ||||
|         this.perLayer.forEach((fs) => { | ||||
|             new SaveFeatureSourceToLocalStorage(fs.layer.layerDef.id, 15, fs) | ||||
|             new SaveFeatureSourceToLocalStorage( | ||||
|                 fs.layer.layerDef.id, | ||||
|                 15, | ||||
|                 fs, | ||||
|                 this.featureProperties | ||||
|             ) | ||||
| 
 | ||||
|             const filtered = new FilteringFeatureSource( | ||||
|                 fs.layer, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue