111 lines
2.8 KiB
TypeScript
111 lines
2.8 KiB
TypeScript
// Copyright (c) 2022 The Brave Authors. All rights reserved.
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
|
export type CacheListener<T> = (
|
|
newValue: T,
|
|
oldValue: T
|
|
) => void
|
|
|
|
/**
|
|
* Allows consumers to subscribe to changes to an object.
|
|
*
|
|
* This is useful for listening to changes to a remote mojom object and
|
|
* keeping track of the intermediate value.
|
|
*/
|
|
export class CachingWrapper<T> {
|
|
protected cache: T
|
|
protected listeners: Array<CacheListener<T>> = []
|
|
|
|
constructor(defaultValue: T) {
|
|
this.cache = defaultValue;
|
|
}
|
|
|
|
addListener(listener: CacheListener<T>, init = true) {
|
|
this.listeners.push(listener)
|
|
if (init) listener(this.cache, {} as any)
|
|
}
|
|
|
|
removeListener(listener: CacheListener<T>) {
|
|
this.listeners = this.listeners.filter((l) => l !== listener)
|
|
}
|
|
|
|
/**
|
|
* Notifies all listeners that the latest value has been updated.
|
|
* @param newValue The new value
|
|
*/
|
|
notifyChanged(newValue: T) {
|
|
const oldValue = this.cache
|
|
this.cache = newValue
|
|
|
|
for (const listener of this.listeners) {
|
|
listener(newValue, oldValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
export type Cache<Entity> = { [key: string]: Entity }
|
|
export type ChangeEvent<Entity> = {
|
|
addedOrUpdated: { [key: string]: Entity }
|
|
removed: string[]
|
|
}
|
|
|
|
/**
|
|
* A helper class for caching a dictionary of entities which may be
|
|
* added/updated/remove.
|
|
*
|
|
* Manages partial updates and notifies listeners of the changes.
|
|
*
|
|
* Example:
|
|
*
|
|
* in example.mojom:
|
|
*
|
|
* struct ExampleEvent {
|
|
* map<string, Entity> addedOrUpdated;
|
|
* array<string> removed;
|
|
* };
|
|
*
|
|
* interface ExampleListener {
|
|
* Changed(ExampleEvent event);
|
|
* };
|
|
*
|
|
* interface Controller {
|
|
* AddListener(pending_remote<ExampleListener> listener);
|
|
* }
|
|
*
|
|
* in exampleCache.ts
|
|
*
|
|
* class ExampleCache
|
|
* extends EntityCachingWrapper<Entity>
|
|
* implements ExampleListenerInterface {
|
|
* private receiver = new ExampleListenerReceiver(this)
|
|
*
|
|
* constructor() {
|
|
* super()
|
|
*
|
|
* // where get controller gets you a bound instance of Controller
|
|
* getController().addExampleListener(this.receiver.$.bindNewPipeAndPassRemove())
|
|
* }
|
|
* }
|
|
*
|
|
* // and use the cache
|
|
* const cache = new ExampleCache()
|
|
* cache.addListener(console.log)
|
|
*/
|
|
export class EntityCachingWrapper<Entity> extends CachingWrapper<Cache<Entity>> {
|
|
constructor() {
|
|
super({})
|
|
}
|
|
|
|
// Note: This function is called via mojom - it should be defined on the
|
|
// listener interface.
|
|
changed(event: ChangeEvent<Entity>) {
|
|
const copy: Cache<Entity> = { ...this.cache }
|
|
for (const id in event.addedOrUpdated) copy[id] = event.addedOrUpdated[id]
|
|
for (const id of event.removed) delete copy[id]
|
|
|
|
this.notifyChanged(copy)
|
|
}
|
|
}
|