@itwin/core-backend
Version:
iTwin.js backend components
284 lines • 12.3 kB
JavaScript
"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