UNPKG

@bitbybit-dev/occt-worker

Version:

Bit By Bit Developers CAD algorithms using OpenCascade Technology kernel adapted for WebWorker

339 lines (338 loc) 15.5 kB
export class CacheHelper { constructor(occ) { this.occ = occ; this.hashesFromPreviousRun = {}; this.usedHashes = {}; this.argCache = {}; } cleanAllCache() { // Clean all entries in argCache, not just usedHashes const allCacheKeys = Object.keys(this.argCache); allCacheKeys.forEach(hash => { if (this.argCache[hash]) { try { const cachedItem = this.argCache[hash]; // Only attempt to clean and delete OCCT objects if (this.isOCCTObject(cachedItem)) { // Handle arrays of OCCT objects if (Array.isArray(cachedItem)) { cachedItem.forEach(item => { try { // Shape-specific cleanup only for TopoDS_Shape objects if (this.isShape(item)) { this.occ.BRepTools_Clean_Force(item, true); this.occ.BRepTools_CleanGeometry(item); } item.delete(); } catch (error) { // Ignore errors for already deleted objects } }); } else { // Shape-specific cleanup only for TopoDS_Shape objects if (this.isShape(cachedItem)) { this.occ.BRepTools_Clean_Force(cachedItem, true); this.occ.BRepTools_CleanGeometry(cachedItem); } cachedItem.delete(); } } } catch (error) { // Ignore errors when cleaning objects that may already be deleted } } }); this.argCache = {}; this.usedHashes = {}; this.hashesFromPreviousRun = {}; } cleanCacheForHash(hash) { if (this.argCache[hash]) { try { const cachedItem = this.argCache[hash]; // Only attempt to clean and delete OCCT objects if (this.isOCCTObject(cachedItem)) { // Handle arrays of OCCT objects if (Array.isArray(cachedItem)) { cachedItem.forEach(item => { try { // Shape-specific cleanup only for TopoDS_Shape objects if (this.isShape(item)) { this.occ.BRepTools_Clean_Force(item, true); this.occ.BRepTools_CleanGeometry(item); } item.delete(); } catch (error) { // Ignore errors for already deleted objects } }); } else { // Shape-specific cleanup only for TopoDS_Shape objects if (this.isShape(cachedItem)) { this.occ.BRepTools_Clean_Force(cachedItem, true); this.occ.BRepTools_CleanGeometry(cachedItem); } cachedItem.delete(); } } } catch (error) { // Ignore errors when cleaning objects that may already be deleted } } delete this.argCache[hash]; delete this.usedHashes[hash]; delete this.hashesFromPreviousRun[hash]; } cleanUpCache() { // Clean up cache entries that were used in previous run but not in current run // This helps manage memory by removing unused cached shapes const usedHashKeys = Object.keys(this.usedHashes); const hashesFromPreviousRunKeys = Object.keys(this.hashesFromPreviousRun); // Find hashes that exist in previous run but not in current run // These are the ones we should clean up let hashesToDelete = []; if (hashesFromPreviousRunKeys.length > 0) { hashesToDelete = hashesFromPreviousRunKeys.filter(hash => !usedHashKeys.includes(hash)); } // Delete unused objects and clean them from cache if (hashesToDelete.length > 0) { hashesToDelete.forEach(hash => { if (this.argCache[hash]) { try { const cachedItem = this.argCache[hash]; // Only try to clean and delete if it's an OCCT object if (this.isOCCTObject(cachedItem)) { // Handle arrays of OCCT objects if (Array.isArray(cachedItem)) { cachedItem.forEach(item => { try { // Shape-specific cleanup only for TopoDS_Shape objects if (this.isShape(item)) { this.occ.BRepTools_Clean_Force(item, true); this.occ.BRepTools_CleanGeometry(item); } item.delete(); } catch (_a) { // Ignore errors for already deleted objects } }); } else { // Shape-specific cleanup only for TopoDS_Shape objects if (this.isShape(cachedItem)) { this.occ.BRepTools_Clean_Force(cachedItem, true); this.occ.BRepTools_CleanGeometry(cachedItem); } cachedItem.delete(); } } } catch (_a) { // Ignore errors for already deleted or invalid objects } delete this.argCache[hash]; } delete this.usedHashes[hash]; }); } // Update hashesFromPreviousRun to be current usedHashes for next cleanup cycle this.hashesFromPreviousRun = Object.assign({}, this.usedHashes); } // eslint-disable-next-line @typescript-eslint/no-explicit-any isOCCTObject(obj) { return obj !== undefined && obj !== null && (!Array.isArray(obj) && obj.$$ !== undefined) || (Array.isArray(obj) && obj.length > 0 && obj[0].$$ !== undefined); } /** * Checks if an object is a TopoDS_Shape (or subclass). * Shapes have ShapeType() method which returns the shape type enum. * This is a performant check - just property access + typeof, no function call. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any isShape(obj) { return obj !== undefined && obj !== null && obj.$$ !== undefined && typeof obj.ShapeType === "function"; } /** * Checks if an object is a non-shape OCCT entity (e.g., document handle, other handles). * These are OCCT objects (have $$) but do NOT have ShapeType() method. * This distinguishes them from TopoDS_Shape objects. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any isEntityHandle(obj) { return obj !== undefined && obj !== null && obj.$$ !== undefined && typeof obj.ShapeType !== "function"; // Key differentiator: entities don't have ShapeType } /** Hashes input arguments and checks the cache for that hash. * It returns a copy of the cached object if it exists, but will * call the `cacheMiss()` callback otherwise. The result will be * added to the cache if `GUIState["Cache?"]` is true. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any cacheOp(args, cacheMiss) { let toReturn = null; const curHash = this.computeHash(args); this.usedHashes[curHash] = curHash; this.hashesFromPreviousRun[curHash] = curHash; const check = this.checkCache(curHash); if (check) { if (this.isOCCTObject(check)) { toReturn = check; toReturn.hash = check.hash; } else if (check.value) { toReturn = check.value; } } else { toReturn = cacheMiss(); if (Array.isArray(toReturn) && this.isOCCTObject(toReturn)) { toReturn.forEach((r, index) => { const itemHash = this.computeHash(Object.assign(Object.assign({}, args), { index })); r.hash = itemHash; this.addToCache(itemHash, r); }); } else { if (this.isOCCTObject(toReturn)) { toReturn.hash = curHash; this.addToCache(curHash, toReturn); } else if (toReturn && toReturn.compound && toReturn.data && toReturn.shapes && toReturn.shapes.length > 0) { // Handle ObjectDefinition structure // eslint-disable-next-line @typescript-eslint/no-explicit-any const objDef = toReturn; const compoundHash = this.computeHash(Object.assign(Object.assign({}, args), { index: "compound" })); objDef.compound.hash = compoundHash; this.addToCache(compoundHash, objDef.compound); objDef.shapes.forEach((s, index) => { const itemHash = this.computeHash(Object.assign(Object.assign({}, args), { index })); s.shape.hash = itemHash; this.addToCache(itemHash, s.shape); }); this.addToCache(curHash, { value: objDef }); } else if (toReturn && typeof toReturn === "object" && "success" in toReturn && "document" in toReturn && this.isEntityHandle(toReturn.document)) { // Handle AssemblyDocumentResult structure - cache the document separately const docHash = this.computeHash(Object.assign(Object.assign({}, args), { index: "document" })); toReturn.document.hash = docHash; this.addToCache(docHash, toReturn.document); this.addToCache(curHash, { value: toReturn }); } else { this.addToCache(curHash, { value: toReturn }); } } } return toReturn; } /** Returns the cached object if it exists and is valid, or null otherwise. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any checkCache(hash) { const cachedShape = this.argCache[hash]; if (!cachedShape) { return null; } // Check if the cached shape is still valid (not deleted) if (this.isOCCTObject(cachedShape)) { // Handle arrays of OCCT objects if (Array.isArray(cachedShape)) { // Check if any shape in the array has been deleted for (const shape of cachedShape) { try { if (shape.IsNull && shape.IsNull()) { // One of the shapes is null, invalidate entire cache entry delete this.argCache[hash]; return null; } } catch (e) { // If calling IsNull() throws an error, the object has been deleted delete this.argCache[hash]; return null; } } } else { // Handle single OCCT object try { // Check if the shape has been deleted by checking if IsNull() can be called // and if the shape is null or invalid if (cachedShape.IsNull && cachedShape.IsNull()) { // Shape is null, remove from cache and return null delete this.argCache[hash]; return null; } } catch (e) { // If calling IsNull() throws an error, the object has been deleted // Remove from cache and return null delete this.argCache[hash]; return null; } } } return cachedShape; } /** Adds this `shape` to the cache, indexable by `hash`. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any addToCache(hash, shape) { const cacheShape = shape; // Only set hash property on objects, not primitives if (cacheShape !== null && typeof cacheShape === "object") { cacheShape.hash = hash; // This is the cached version of the object } this.argCache[hash] = cacheShape; return hash; } /** This function computes a 32-bit integer hash given a set of `arguments`. * If `raw` is true, the raw set of sanitized arguments will be returned instead. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any computeHash(args, raw) { let argsString = JSON.stringify(args); argsString = argsString.replace(/("ptr":(-?[0-9]*?),)/g, ""); argsString = argsString.replace(/("ptr":(-?[0-9]*))/g, ""); if (argsString.includes("ptr")) { console.error("YOU DONE MESSED UP YOUR REGEX."); } const hashString = Math.random.toString() + argsString; if (raw) { return hashString; } return this.stringToHash(hashString); } /** This function converts a string to a 32bit integer. */ stringToHash(str) { let hash = 0; if (str.length === 0) { return hash; } for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); // tslint:disable-next-line: no-bitwise hash = ((hash << 5) - hash) + char; // tslint:disable-next-line: no-bitwise hash = hash & hash; } return hash; } /** This function returns a version of the `inputArray` without the `objectToRemove`. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any remove(inputArray, objectToRemove) { return inputArray.filter((el) => { return el.hash !== objectToRemove.hash || el.ptr !== objectToRemove.ptr; }); } }