UNPKG

@speckle/objectloader2

Version:

This is an updated objectloader for the Speckle viewer written in typescript

161 lines (146 loc) 4.87 kB
import { DeferredBase } from './deferredBase.js' import { Base, CustomLogger, Item } from '../types/types.js' import { DefermentManagerOptions } from '../operations/options.js' export class DefermentManager { private deferments: Map<string, DeferredBase> = new Map() private timer?: ReturnType<typeof setTimeout> private logger: CustomLogger private currentSize = 0 private disposed = false //tracks total deferment requests for each id //this is used to prevent cleaning up deferments that are still being requested private totalDefermentRequests: Map<string, number> = new Map() constructor(private options: DefermentManagerOptions) { this.resetGlobalTimer() this.logger = options.logger || ((): void => {}) } private now(): number { return Date.now() } isDeferred(id: string): boolean { return this.deferments.has(id) } get(id: string): DeferredBase | undefined { if (this.disposed) throw new Error('DefermentManager is disposed') return this.deferments.get(id) } async defer(params: { id: string }): Promise<Base> { if (this.disposed) throw new Error('DefermentManager is disposed') this.trackDefermentRequest(params.id) const now = this.now() const deferredBase = this.deferments.get(params.id) if (deferredBase) { deferredBase.setAccess(now) return deferredBase.getPromise() } const notYetFound = new DeferredBase( this.options.ttlms, params.id, now + this.options.ttlms ) this.deferments.set(params.id, notYetFound) return notYetFound.getPromise() } private trackDefermentRequest(id: string): void { const request = this.totalDefermentRequests.get(id) if (request) { this.totalDefermentRequests.set(id, request + 1) } else { this.totalDefermentRequests.set(id, 1) } } undefer(item: Item): void { if (this.disposed) throw new Error('DefermentManager is disposed') const now = this.now() this.currentSize += item.size || 0 //order matters here with found before undefer const deferredBase = this.deferments.get(item.baseId) if (deferredBase) { deferredBase.found(item) deferredBase.setAccess(now) } else { const existing = new DeferredBase(this.options.ttlms, item.baseId, now) existing.found(item) this.deferments.set(item.baseId, existing) } } private resetGlobalTimer(): void { const run = (): void => { this.cleanDeferments() this.timer = setTimeout(run, this.options.ttlms) } this.timer = setTimeout(run, this.options.ttlms) } dispose(): void { if (this.disposed) return this.disposed = true if (this.timer) { clearTimeout(this.timer) this.timer = undefined } this.clearDeferments() } private clearDeferments(): void { let waiting = 0 for (const deferredBase of this.deferments.values()) { deferredBase.done(0) if (deferredBase.getItem() === undefined) { waiting++ } } this.currentSize = 0 this.deferments.clear() this.logger('cleared deferments, left', waiting) } private cleanDeferments(): void { const maxSizeBytes = this.options.maxSizeInMb * 1024 * 1024 if (this.currentSize < maxSizeBytes) { this.logger( 'deferments size is ok, no need to clean', this.currentSize, maxSizeBytes ) return } const now = this.now() let cleaned = 0 const start = performance.now() for (const deferredBase of Array.from(this.deferments.values()) .filter((x) => x.isExpired(now)) .sort((a, b) => this.compareMaybeBasesBySize(a.getItem(), b.getItem()))) { if (deferredBase.done(now)) { //if the deferment is done but has been requested multiple times, //we do not clean it up to allow the requests to resolve const requestCount = this.totalDefermentRequests.get(deferredBase.getId()) if (requestCount && requestCount > 1) { return } this.currentSize -= deferredBase.getItem()?.size || 0 this.deferments.delete(deferredBase.getId()) cleaned++ if (this.currentSize < maxSizeBytes) { break } } } this.logger( 'cleaned deferments: cleaned, left, time', cleaned, this.deferments.size, performance.now() - start ) return } compareMaybeBasesBySize(a: Item | undefined, b: Item | undefined): number { if (a === undefined && b === undefined) return 0 if (a === undefined) return -1 if (b === undefined) return 1 return this.compareMaybe(a.size, b.size) } compareMaybe(a: number | undefined, b: number | undefined): number { if (a === undefined && b === undefined) return 0 if (a === undefined) return -1 if (b === undefined) return 1 return a - b } }