UNPKG

@speckle/objectloader2

Version:

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

162 lines 5.52 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MemoryCache = exports.MemoryCacheItem = void 0; class MemoryCacheItem { item; expiresAt; // Timestamp in ms constructor(item, expiresAt) { this.item = item; this.expiresAt = expiresAt; } isExpired(now) { return now > this.expiresAt; } setAccess(now, ttl) { this.expiresAt = now + ttl; } getItem() { return this.item; } done(now) { if (this.isExpired(now)) { return true; } return false; } } exports.MemoryCacheItem = MemoryCacheItem; class MemoryCache { isGathered = new Map(); references = new Map(); cache = new Map(); options; logger; disposed = false; currentSize = 0; timer; constructor(options, logger) { this.options = options; this.logger = logger; this.resetGlobalTimer(); } add(item, requestItem, testNow) { if (this.disposed) throw new Error('MemoryCache is disposed'); this.currentSize += item.size || 0; this.cache.set(item.baseId, new MemoryCacheItem(item, (testNow || this.now()) + this.options.ttlms)); if (!this.isGathered.has(item.baseId)) { this.isGathered.set(item.baseId, true); this.scanForReferences(item.base, requestItem); } } get(id) { if (this.disposed) throw new Error('MemoryCache is disposed'); const item = this.cache.get(id); if (item) { item.setAccess(this.now(), this.options.ttlms); return item.getItem(); } return undefined; } scanForReferences(data, requestItem) { const scan = (item) => { // Stop if the item is null or not an object (i.e., primitive) if (item === null || typeof item !== 'object') { return; } // If it's an array, scan each element if (Array.isArray(item)) { for (const element of item) { scan(element); } return; } // If it's an object, scan its properties for (const key in item) { if (Object.prototype.hasOwnProperty.call(item, key)) { // We found the target property! if (key === 'referencedId') { const value = item.referencedId; // Ensure the value is a string before adding it if (typeof value === 'string') { this.references.set(value, (this.references.get(value) || 0) + 1); if (!this.cache.has(value)) { requestItem(value); } } } // Continue scanning deeper into the object's properties scan(item[key]); } } }; scan(data); } resetGlobalTimer() { const run = () => { this.cleanCache(); this.timer = setTimeout(run, this.options.ttlms); }; this.timer = setTimeout(run, this.options.ttlms); } now() { return Date.now(); } cleanCache(testNow) { const maxSizeBytes = this.options.maxSizeInMb * 1024 * 1024; if (this.currentSize < maxSizeBytes) { this.logger(`cache size (${this.currentSize} < ${maxSizeBytes}) is ok, no need to clean`); return; } const now = testNow || this.now(); let cleaned = 0; const start = performance.now(); for (const deferredBase of Array.from(this.cache.values()) .filter((x) => x.isExpired(now)) .sort((a, b) => this.compareMaybeBasesByReferences(a.getItem().baseId, b.getItem().baseId))) { if (deferredBase.done(now)) { const id = deferredBase.getItem().baseId; const referenceCount = this.references.get(id) || 0; if (referenceCount > 0) { // Skip eviction for items with reference counts greater than 0, // as they are still in use and should not be removed from the cache. continue; } this.currentSize -= deferredBase.getItem().size || 0; this.cache.delete(id); cleaned++; if (this.currentSize < maxSizeBytes) { break; } } } this.logger(`cleaned cache: cleaned ${cleaned}, cached ${this.cache.size}, time ${performance.now() - start}`); return; } compareMaybeBasesByReferences(id1, id2) { const a = this.references.get(id1); const b = this.references.get(id2); if (a === undefined && b === undefined) return 0; if (a === undefined) return -1; if (b === undefined) return 1; return a - b; } dispose() { if (this.disposed) return; this.disposed = true; if (this.timer) { clearTimeout(this.timer); this.timer = undefined; } this.cache.clear(); this.isGathered.clear(); this.references.clear(); } } exports.MemoryCache = MemoryCache; //# sourceMappingURL=MemoryCache.js.map