@speckle/objectloader2
Version:
This is an updated objectloader for the Speckle viewer written in typescript
162 lines • 5.52 kB
JavaScript
"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