UNPKG

@itwin/core-backend

Version:
284 lines • 12.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InstanceKeyLRUCache = exports.ElementLRUCache = void 0; const core_bentley_1 = require("@itwin/core-bentley"); /** * A LRU cache for entities. Cache contains the ElementProps and the load options used to load it. * Cache can be searched by id, code or federationGuid. */ class ElementLRUCache { capacity; static DEFAULT_CAPACITY = 2000; _elementCache = new Map(); _cacheByCode = new Map(); _cacheByFederationGuid = new Map(); static makeCodeKey(code) { const keys = [code.scope, code.spec]; if (code.value !== undefined) { keys.push(code.value); } return JSON.stringify(keys); } findElement(key) { if (key.id) { return this._elementCache.get(key.id); } else if (key.federationGuid) { const id = this._cacheByFederationGuid.get(key.federationGuid); if (id) return this._elementCache.get(id); } else if (key.code) { const id = this._cacheByCode.get(ElementLRUCache.makeCodeKey(key.code)); if (id) return this._elementCache.get(id); } else { core_bentley_1.ITwinError.throwError({ message: "No key provided", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } }); } return undefined; } constructor(capacity = ElementLRUCache.DEFAULT_CAPACITY) { this.capacity = capacity; } clear() { this._elementCache.clear(); this._cacheByCode.clear(); this._cacheByFederationGuid.clear(); } get size() { return this._elementCache.size; } deleteWithModel(modelId) { this._elementCache.forEach((cachedVal, elementId) => { if (cachedVal.elProps.model === modelId) this.delete({ id: elementId }); }); } delete(key) { const cachedElement = this.findElement(key); if (cachedElement) { if (cachedElement.elProps.id) this._elementCache.delete(cachedElement.elProps.id); this._cacheByCode.delete(ElementLRUCache.makeCodeKey(cachedElement.elProps.code)); if (cachedElement.elProps.federationGuid) this._cacheByFederationGuid.delete(cachedElement.elProps.federationGuid); return true; } return false; } get(key) { const cachedElement = this.findElement(key); if (cachedElement) { if (cachedElement.elProps.id) { this._elementCache.delete(cachedElement.elProps.id); this._elementCache.set(cachedElement.elProps.id, cachedElement); } } if (cachedElement) { if (key.displayStyle !== cachedElement.loadOptions.displayStyle || key.onlyBaseProperties !== cachedElement.loadOptions.onlyBaseProperties || key.renderTimeline !== cachedElement.loadOptions.renderTimeline || key.wantBRepData !== cachedElement.loadOptions.wantBRepData || key.wantGeometry !== cachedElement.loadOptions.wantGeometry) { return undefined; } } return cachedElement; } set(el) { if (!el.elProps.id) core_bentley_1.ITwinError.throwError({ message: "Element must have an id", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } }); // do not cache this element as geom need to be rerender into geom props if any part changes // TODO: find a way to handle caching geometry if (el.loadOptions.wantGeometry) { return this; } if (this._elementCache.has(el.elProps.id)) { this._elementCache.delete(el.elProps.id); this._elementCache.set(el.elProps.id, el); } else { this._elementCache.set(el.elProps.id, el); } if (el.elProps.federationGuid) { this._cacheByFederationGuid.set(el.elProps.federationGuid, el.elProps.id); } this._cacheByCode.set(ElementLRUCache.makeCodeKey(el.elProps.code), el.elProps.id); if (this._elementCache.size > this.capacity) { const oldestKey = this._elementCache.keys().next().value; const oldestElement = this._elementCache.get(oldestKey); this.delete({ id: oldestKey, federationGuid: oldestElement?.elProps.federationGuid, code: oldestElement?.elProps.code }); } return this; } get [Symbol.toStringTag]() { return `EntityCache(this.size=${this.size}, capacity=${this.capacity})`; } } exports.ElementLRUCache = ElementLRUCache; ; /** * A map to store instance keys based on different optional arguments like id, code, or federationGuid. * This allows the cache to be searched by any combination of arguments. */ class ArgumentsToResultMap { _idToResult = new Map(); _codeToResult = new Map(); _federationGuidToResult = new Map(); constructor() { } set(args, result) { if (!args.id && !args.code && !args.federationGuid) core_bentley_1.ITwinError.throwError({ message: "At least one id, code, or federationGuid must be provided", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } }); if (args.id) this._idToResult.set(args.id, result); else this._idToResult.set(result.id, result); if (args.code) this._codeToResult.set(args.code, result); if (args.federationGuid) this._federationGuidToResult.set(args.federationGuid, result); } get(args) { if (!args.id && !args.code && !args.federationGuid) core_bentley_1.ITwinError.throwError({ message: "At least one id, code, or federationGuid must be provided", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } }); if (args.id) return this._idToResult.get(args.id); else if (args.code) return this._codeToResult.get(args.code); else if (args.federationGuid) return this._federationGuidToResult.get(args.federationGuid); return undefined; } delete(args) { if (!args.id && !args.code && !args.federationGuid) core_bentley_1.ITwinError.throwError({ message: "At least one id, code, or federationGuid must be provided", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } }); let deleted = false; if (args.id) deleted = this._idToResult.delete(args.id) ? true : deleted; if (args.code) deleted = this._codeToResult.delete(args.code) ? true : deleted; if (args.federationGuid) deleted = this._federationGuidToResult.delete(args.federationGuid) ? true : deleted; return deleted; } clear() { this._idToResult.clear(); this._codeToResult.clear(); this._federationGuidToResult.clear(); } get size() { return this._idToResult.size; // Id to key will always be stored even if code or federationGuid are not provided. } get [Symbol.toStringTag]() { return `idCacheSize=${this._idToResult.size}, codeCacheSize=${this._codeToResult.size}, federationGuidCacheSize=${this._federationGuidToResult.size}`; } } /** * A LRU cache for entities. Cache contains the ElementProps and the load options used to load it. * Cache can be searched by id, code or federationGuid. */ class InstanceKeyLRUCache { capacity; static DEFAULT_CAPACITY = 2000; _argsToResultCache = new ArgumentsToResultMap(); _resultToArgsCache = new Map(); constructor(capacity = ElementLRUCache.DEFAULT_CAPACITY) { this.capacity = capacity; } set(key, result) { const cacheArgs = InstanceKeyLRUCache.makeCachedArgs(key); cacheArgs.id = cacheArgs.id ? cacheArgs.id : result.id; const existingArgs = this._resultToArgsCache.get(result.id); if (existingArgs) { // Combine existing args with new args for more complete key this._argsToResultCache.delete(existingArgs); const combinedArgs = InstanceKeyLRUCache.combineCachedArgs(existingArgs, cacheArgs); this._argsToResultCache.set(combinedArgs, result); this._resultToArgsCache.set(result.id, combinedArgs); } else { this._argsToResultCache.set(cacheArgs, result); this._resultToArgsCache.set(result.id, cacheArgs); } if (this._resultToArgsCache.size > this.capacity) { const oldestKey = this._resultToArgsCache.keys().next().value; const oldestArgs = this._resultToArgsCache.get(oldestKey); this.deleteCachedArgs({ id: oldestArgs?.id, code: oldestArgs?.code, federationGuid: oldestArgs?.federationGuid }); } return this; } get(key) { const args = InstanceKeyLRUCache.makeCachedArgs(key); const cachedResult = this._argsToResultCache.get(args); if (cachedResult) { // Pop the cached result to the end of the cache to mark it as recently used const cachedArgs = this._resultToArgsCache.get(cachedResult.id); if (cachedArgs) { this._resultToArgsCache.delete(cachedResult.id); this._resultToArgsCache.set(cachedResult.id, cachedArgs); } } return cachedResult; } deleteById(id) { if (!core_bentley_1.Id64.isValidId64(id)) core_bentley_1.ITwinError.throwError({ message: "Invalid id provided", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } }); const cacheArgs = { id: core_bentley_1.Id64.fromString(id), }; return this.deleteCachedArgs(cacheArgs); } delete(key) { const cacheArgs = InstanceKeyLRUCache.makeCachedArgs(key); return this.deleteCachedArgs(cacheArgs); } deleteCachedArgs(key) { const result = this._argsToResultCache.get(key); if (result) { const argsToDelete = this._resultToArgsCache.get(result.id); this._argsToResultCache.delete({ id: argsToDelete?.id, code: argsToDelete?.code, federationGuid: argsToDelete?.federationGuid }); this._resultToArgsCache.delete(result.id); return true; } return false; } static makeCachedArgs(args) { if (!args.partialKey && !args.code && !args.federationGuid) core_bentley_1.ITwinError.throwError({ message: "ResolveInstanceKeyArgs must have a partialKey, code, or federationGuid", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } }); return { id: args.partialKey?.id, code: args.code ? InstanceKeyLRUCache.makeCodeKey(args.code) : undefined, federationGuid: args.federationGuid, }; } static combineCachedArgs(originalArgs, newArgs) { if (!originalArgs.id && !originalArgs.code && !originalArgs.federationGuid && !newArgs.id && !newArgs.code && !newArgs.federationGuid) core_bentley_1.ITwinError.throwError({ message: "ResolveInstanceKeyArgs must have a partialKey, code, or federationGuid", iTwinErrorId: { scope: "imodel-cache", key: "invalid-arguments" } }); return { id: newArgs.id ? newArgs.id : originalArgs.id, code: newArgs.code ? newArgs.code : originalArgs.code, federationGuid: newArgs.federationGuid ? newArgs.federationGuid : originalArgs.federationGuid, }; } static makeCodeKey(code) { const keys = [code.scope, code.spec]; if (code.value !== undefined) { keys.push(code.value); } return JSON.stringify(keys); } clear() { this._argsToResultCache.clear(); this._resultToArgsCache.clear(); } get size() { return this._resultToArgsCache.size; } get [Symbol.toStringTag]() { return `InstanceKeyCache(size=${this.size}, capacity=${this.capacity})`; } } exports.InstanceKeyLRUCache = InstanceKeyLRUCache; ; //# sourceMappingURL=ElementLRUCache.js.map