@@ -4,74 +4,247 @@ export class CacheHelper {
44 hashesFromPreviousRun = { } ;
55 usedHashes = { } ;
66 argCache = { } ;
7-
8- constructor ( ) { }
7+ jscadObjectHashes = new Set < string | number > ( ) ; // Track which hashes contain JSCAD objects
98
109 cleanAllCache ( ) : void {
11- const usedHashKeys = Object . keys ( this . usedHashes ) ;
10+ // Clean all entries in argCache, not just usedHashes
11+ const allCacheKeys = Object . keys ( this . argCache ) ;
1212
13- usedHashKeys . forEach ( hash => {
13+ allCacheKeys . forEach ( hash => {
1414 if ( this . argCache [ hash ] ) {
1515 try {
16- this . argCache [ hash ] . delete ( ) ;
16+ const cachedItem = this . argCache [ hash ] ;
17+ // Only attempt to delete JSCAD objects
18+ if ( this . isJSCADObject ( cachedItem ) ) {
19+ // Handle arrays of JSCAD objects
20+ if ( Array . isArray ( cachedItem ) ) {
21+ cachedItem . forEach ( obj => {
22+ try {
23+ if ( obj . delete ) {
24+ obj . delete ( ) ;
25+ }
26+ } catch ( error ) {
27+ // Ignore errors for already deleted objects
28+ }
29+ } ) ;
30+ } else {
31+ if ( cachedItem . delete ) {
32+ cachedItem . delete ( ) ;
33+ }
34+ }
35+ }
1736 }
18- // eslint-disable-next-line no-empty
19- catch {
37+ catch ( error ) {
38+ // Ignore errors when cleaning objects that may already be deleted
2039 }
2140 }
2241 } ) ;
2342
2443 this . argCache = { } ;
2544 this . usedHashes = { } ;
2645 this . hashesFromPreviousRun = { } ;
46+ this . jscadObjectHashes . clear ( ) ;
47+ }
48+
49+ cleanCacheForHash ( hash : string ) : void {
50+ if ( this . argCache [ hash ] ) {
51+ try {
52+ const cachedItem = this . argCache [ hash ] ;
53+ // Only attempt to delete JSCAD objects
54+ if ( this . isJSCADObject ( cachedItem ) ) {
55+ // Handle arrays of JSCAD objects
56+ if ( Array . isArray ( cachedItem ) ) {
57+ cachedItem . forEach ( obj => {
58+ try {
59+ if ( obj . delete ) {
60+ obj . delete ( ) ;
61+ }
62+ } catch ( error ) {
63+ // Ignore errors for already deleted objects
64+ }
65+ } ) ;
66+ } else {
67+ if ( cachedItem . delete ) {
68+ cachedItem . delete ( ) ;
69+ }
70+ }
71+ }
72+ }
73+ catch ( error ) {
74+ // Ignore errors when cleaning objects that may already be deleted
75+ }
76+ }
77+ delete this . argCache [ hash ] ;
78+ delete this . usedHashes [ hash ] ;
79+ delete this . hashesFromPreviousRun [ hash ] ;
80+ this . jscadObjectHashes . delete ( hash ) ;
81+ }
82+
83+ cleanUpCache ( ) : void {
84+ // Clean up cache entries that were used in previous run but not in current run
85+ // This helps manage memory by removing unused cached objects
86+
87+ const usedHashKeys = Object . keys ( this . usedHashes ) ;
88+ const hashesFromPreviousRunKeys = Object . keys ( this . hashesFromPreviousRun ) ;
89+
90+ // Find hashes that exist in previous run but not in current run
91+ // These are the ones we should clean up
92+ let hashesToDelete : string [ ] = [ ] ;
93+ if ( hashesFromPreviousRunKeys . length > 0 ) {
94+ hashesToDelete = hashesFromPreviousRunKeys . filter ( hash => ! usedHashKeys . includes ( hash ) ) ;
95+ }
96+
97+ // Delete unused objects and clean them from cache
98+ if ( hashesToDelete . length > 0 ) {
99+ hashesToDelete . forEach ( hash => {
100+ if ( this . argCache [ hash ] ) {
101+ try {
102+ const obj = this . argCache [ hash ] ;
103+ // Only try to delete if it's a JSCAD object
104+ if ( this . isJSCADObject ( obj ) ) {
105+ // Handle arrays of JSCAD objects
106+ if ( Array . isArray ( obj ) ) {
107+ obj . forEach ( o => {
108+ try {
109+ if ( o . delete ) {
110+ o . delete ( ) ;
111+ }
112+ } catch {
113+ // Ignore errors for already deleted objects
114+ }
115+ } ) ;
116+ } else {
117+ if ( obj . delete ) {
118+ obj . delete ( ) ;
119+ }
120+ }
121+ }
122+ } catch {
123+ // Ignore errors for already deleted or invalid objects
124+ }
125+ delete this . argCache [ hash ] ;
126+ }
127+ delete this . usedHashes [ hash ] ;
128+ this . jscadObjectHashes . delete ( hash ) ;
129+ } ) ;
130+ }
131+
132+ // Update hashesFromPreviousRun to be current usedHashes for next cleanup cycle
133+ this . hashesFromPreviousRun = { ...this . usedHashes } ;
27134 }
28135
136+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
137+ isJSCADObject ( obj : any ) : boolean {
138+ // JSCAD objects typically have specific properties or methods
139+ // Check for common JSCAD object characteristics
140+ return obj !== undefined && obj !== null && (
141+ ( ! Array . isArray ( obj ) && typeof obj === "object" && obj . delete !== undefined ) ||
142+ ( Array . isArray ( obj ) && obj . length > 0 && typeof obj [ 0 ] === "object" && obj [ 0 ] . delete !== undefined )
143+ ) ;
144+ }
29145
30- cacheOp ( args , cacheMiss ) : any {
146+ /** Hashes input arguments and checks the cache for that hash.
147+ * It returns a copy of the cached object if it exists, but will
148+ * call the `cacheMiss()` callback otherwise. The result will be
149+ * added to the cache.
150+ */
151+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
152+ cacheOp ( args : any , cacheMiss : ( ) => any ) : any {
31153 let toReturn = null ;
32154 const curHash = this . computeHash ( args ) ;
33155 this . usedHashes [ curHash ] = curHash ;
34156 this . hashesFromPreviousRun [ curHash ] = curHash ;
35157 const check = this . checkCache ( curHash ) ;
36158 if ( check ) {
37- // TODO I need to check if and why cloning is required.
38- // toReturn = new this.occ.TopoDS_Shape(check);
39- toReturn = check ;
40- toReturn . hash = check . hash ;
159+ if ( this . isJSCADObject ( check ) ) {
160+ toReturn = check ;
161+ toReturn . hash = check . hash ;
162+ } else if ( check . value ) {
163+ toReturn = check . value ;
164+ }
41165 } else {
42166 toReturn = cacheMiss ( ) ;
43- toReturn . hash = curHash ;
44- this . addToCache ( curHash , toReturn ) ;
167+ if ( Array . isArray ( toReturn ) && this . isJSCADObject ( toReturn ) ) {
168+ toReturn . forEach ( ( r , index ) => {
169+ const itemHash = this . computeHash ( { ...args , index } ) ;
170+ r . hash = itemHash ;
171+ this . addToCache ( itemHash , r ) ;
172+ // Track individual element hashes so they can be cleaned up
173+ this . usedHashes [ itemHash ] = itemHash ;
174+ this . hashesFromPreviousRun [ itemHash ] = itemHash ;
175+ } ) ;
176+ } else {
177+ if ( this . isJSCADObject ( toReturn ) ) {
178+ toReturn . hash = curHash ;
179+ this . addToCache ( curHash , toReturn ) ;
180+ }
181+ else {
182+ this . addToCache ( curHash , { value : toReturn } ) ;
183+ }
184+ }
45185 }
46186 return toReturn ;
47187 }
48- /** Returns the cached object if it exists, or null otherwise. */
49- checkCache ( hash ) : any {
50- return this . argCache [ hash ] || null ;
188+ /** Returns the cached object if it exists and is valid, or null otherwise. */
189+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
190+ checkCache ( hash : string | number ) : any {
191+ const cachedObject = this . argCache [ hash ] ;
192+ if ( ! cachedObject ) {
193+ return null ;
194+ }
195+
196+ // For wrapped values (non-JSCAD objects stored as { value: ... })
197+ if ( cachedObject . value !== undefined && ! this . isJSCADObject ( cachedObject ) ) {
198+ return cachedObject ;
199+ }
200+
201+ // If this hash was tracked as a JSCAD object, verify it's still valid
202+ if ( this . jscadObjectHashes . has ( hash ) ) {
203+ const isStillValid = this . isJSCADObject ( cachedObject ) ;
204+ if ( ! isStillValid ) {
205+ // Object was a JSCAD object but is no longer valid
206+ delete this . argCache [ hash ] ;
207+ this . jscadObjectHashes . delete ( hash ) ;
208+ return null ;
209+ }
210+ }
211+
212+ return cachedObject ;
51213 }
52- /** Adds this `shape` to the cache, indexable by `hash`. */
53- addToCache ( hash , shape ) : any {
54- const cacheShape = shape ;
55- cacheShape . hash = hash ;
56- this . argCache [ hash ] = cacheShape ;
214+ /** Adds this `object` to the cache, indexable by `hash`. */
215+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
216+ addToCache ( hash : string | number , object : any ) : string | number {
217+ const cacheObject = object ;
218+ // Only set hash property on objects, not primitives
219+ if ( cacheObject !== null && typeof cacheObject === "object" ) {
220+ cacheObject . hash = hash ;
221+ }
222+ this . argCache [ hash ] = cacheObject ;
223+
224+ // Track if this is a JSCAD object
225+ if ( this . isJSCADObject ( cacheObject ) ) {
226+ this . jscadObjectHashes . add ( hash ) ;
227+ }
228+
57229 return hash ;
58230 }
59231
60232 /** This function computes a 32-bit integer hash given a set of `arguments`.
61- * If `raw` is true, the raw set of sanitized arguments will be returned instead.
233+ * If `raw` is true, the raw set of sanitized arguments will be returned instead.
62234 */
63- computeHash ( args , raw ?: any ) : any {
235+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
236+ computeHash ( args : any , raw ?: boolean ) : number | string {
64237 let argsString = JSON . stringify ( args ) ;
65- argsString = argsString . replace ( / ( \ "p t r \" \ :( - ? [ 0 - 9 ] * ?) \ ,) / g, "" ) ;
66- argsString = argsString . replace ( / ( \ "p t r \" \ :( - ? [ 0 - 9 ] * ) ) / g, "" ) ;
238+ argsString = argsString . replace ( / ( " p t r " : ( - ? [ 0 - 9 ] * ?) , ) / g, "" ) ;
239+ argsString = argsString . replace ( / ( " p t r " : ( - ? [ 0 - 9 ] * ) ) / g, "" ) ;
67240 if ( argsString . includes ( "ptr" ) ) { console . error ( "YOU DONE MESSED UP YOUR REGEX." ) ; }
68241 const hashString = Math . random . toString ( ) + argsString ;
69242 if ( raw ) { return hashString ; }
70243 return this . stringToHash ( hashString ) ;
71244 }
72245
73246 /** This function converts a string to a 32bit integer. */
74- stringToHash ( str : string ) : any {
247+ stringToHash ( str : string ) : number {
75248 let hash = 0 ;
76249 if ( str . length === 0 ) { return hash ; }
77250 for ( let i = 0 ; i < str . length ; i ++ ) {
@@ -85,8 +258,11 @@ export class CacheHelper {
85258 }
86259
87260 /** This function returns a version of the `inputArray` without the `objectToRemove`. */
88- remove ( inputArray , objectToRemove ) : any {
261+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
262+ remove ( inputArray : any [ ] , objectToRemove : any ) : any [ ] {
89263 return inputArray . filter ( ( el ) => {
264+ // Keep elements where hash is different OR ptr is different
265+ // (remove only when BOTH hash AND ptr match)
90266 return el . hash !== objectToRemove . hash ||
91267 el . ptr !== objectToRemove . ptr ;
92268 } ) ;
0 commit comments