@@ -52,9 +52,10 @@ class LRUCache {
5252 this . unloadingHandle = - 1 ;
5353 this . cachedBytes = 0 ;
5454 this . bytesMap = new Map ( ) ;
55+ this . loadedSet = new Set ( ) ;
5556
5657 this . _unloadPriorityCallback = null ;
57- this . getMemoryUsageCallback = ( ) => 0 ;
58+ this . computeMemoryUsageCallback = ( ) => null ;
5859
5960 const itemSet = this . itemSet ;
6061 this . defaultPriorityCallback = item => itemSet . get ( item ) ;
@@ -68,6 +69,12 @@ class LRUCache {
6869
6970 }
7071
72+ getMemoryUsage ( item ) {
73+
74+ return this . bytesMap . get ( item ) ?? null ;
75+
76+ }
77+
7178 add ( item , removeCb ) {
7279
7380 if ( this . markUnusedQueued ) {
@@ -98,8 +105,9 @@ class LRUCache {
98105 itemSet . set ( item , Date . now ( ) ) ;
99106 callbacks . set ( item , removeCb ) ;
100107
101- const bytes = this . getMemoryUsageCallback ( item ) ;
102- this . cachedBytes += bytes ;
108+ // computeMemoryUsageCallback can return "null" if memory usage is not known, yet
109+ const bytes = this . computeMemoryUsageCallback ( item ) ;
110+ this . cachedBytes += bytes || 0 ;
103111 bytesMap . set ( item , bytes ) ;
104112
105113 return true ;
@@ -113,10 +121,11 @@ class LRUCache {
113121 const itemList = this . itemList ;
114122 const bytesMap = this . bytesMap ;
115123 const callbacks = this . callbacks ;
124+ const loadedSet = this . loadedSet ;
116125
117126 if ( itemSet . has ( item ) ) {
118127
119- this . cachedBytes -= bytesMap . get ( item ) ;
128+ this . cachedBytes -= bytesMap . get ( item ) || 0 ;
120129 bytesMap . delete ( item ) ;
121130
122131 callbacks . get ( item ) ( item ) ;
@@ -126,6 +135,7 @@ class LRUCache {
126135 usedSet . delete ( item ) ;
127136 itemSet . delete ( item ) ;
128137 callbacks . delete ( item ) ;
138+ loadedSet . delete ( item ) ;
129139
130140 return true ;
131141
@@ -135,6 +145,28 @@ class LRUCache {
135145
136146 }
137147
148+ // Marks whether tiles in the cache have been completely loaded or not. Tiles that have not been completely
149+ // loaded are subject to being disposed early if the cache is full above its max size limits, even if they
150+ // are marked as used.
151+ setLoaded ( item , value ) {
152+
153+ const { itemSet, loadedSet } = this ;
154+ if ( itemSet . has ( item ) ) {
155+
156+ if ( value === true ) {
157+
158+ loadedSet . add ( item ) ;
159+
160+ } else {
161+
162+ loadedSet . delete ( item ) ;
163+
164+ }
165+
166+ }
167+
168+ }
169+
138170 updateMemoryUsage ( item ) {
139171
140172 const itemSet = this . itemSet ;
@@ -145,9 +177,9 @@ class LRUCache {
145177
146178 }
147179
148- this . cachedBytes -= bytesMap . get ( item ) ;
180+ this . cachedBytes -= bytesMap . get ( item ) || 0 ;
149181
150- const bytes = this . getMemoryUsageCallback ( item ) ;
182+ const bytes = this . computeMemoryUsageCallback ( item ) ;
151183 bytesMap . set ( item , bytes ) ;
152184 this . cachedBytes += bytes ;
153185
@@ -196,32 +228,44 @@ class LRUCache {
196228 itemList,
197229 itemSet,
198230 usedSet,
231+ loadedSet,
199232 callbacks,
200233 bytesMap,
201234 minBytesSize,
202235 maxBytesSize,
203236 } = this ;
204237
205238 const unused = itemList . length - usedSet . size ;
239+ const unloaded = itemList . length - loadedSet . size ;
206240 const excessNodes = Math . max ( Math . min ( itemList . length - minSize , unused ) , 0 ) ;
207241 const excessBytes = this . cachedBytes - minBytesSize ;
208242 const unloadPriorityCallback = this . unloadPriorityCallback || this . defaultPriorityCallback ;
209243 let needsRerun = false ;
210244
211- const hasNodesToUnload = excessNodes > 0 && unused > 0 || itemList . length > maxSize ;
212- const hasBytesToUnload = unused && this . cachedBytes > minBytesSize || this . cachedBytes > maxBytesSize ;
245+ const hasNodesToUnload = excessNodes > 0 && unused > 0 || unloaded && itemList . length > maxSize ;
246+ const hasBytesToUnload = unused && this . cachedBytes > minBytesSize || unloaded && this . cachedBytes > maxBytesSize ;
213247 if ( hasBytesToUnload || hasNodesToUnload ) {
214248
215- // used items should be at the end of the array
249+ // used items should be at the end of the array, "unloaded" items in the middle of the array
216250 itemList . sort ( ( a , b ) => {
217251
218252 const usedA = usedSet . has ( a ) ;
219253 const usedB = usedSet . has ( b ) ;
220254 if ( usedA === usedB ) {
221255
222- // Use the sort function otherwise
223- // higher priority should be further to the left
224- return - unloadPriorityCallback ( a , b ) ;
256+ const loadedA = loadedSet . has ( a ) ;
257+ const loadedB = loadedSet . has ( b ) ;
258+ if ( loadedA === loadedB ) {
259+
260+ // Use the sort function otherwise
261+ // higher priority should be further to the left
262+ return - unloadPriorityCallback ( a , b ) ;
263+
264+ } else {
265+
266+ return loadedA ? 1 : - 1 ;
267+
268+ }
225269
226270 } else {
227271
@@ -241,27 +285,44 @@ class LRUCache {
241285
242286 let removedNodes = 0 ;
243287 let removedBytes = 0 ;
244- while ( true ) {
288+
289+ // evict up to the max node or bytes size, keeping one more item over the max bytes limit
290+ // so the "full" function behaves correctly.
291+ while (
292+ this . cachedBytes - removedBytes > maxBytesSize ||
293+ itemList . length - removedNodes > maxSize
294+ ) {
245295
246296 const item = itemList [ removedNodes ] ;
247- const bytes = bytesMap . get ( item ) ;
297+ const bytes = bytesMap . get ( item ) || 0 ;
298+ if (
299+ usedSet . has ( item ) && loadedSet . has ( item ) ||
300+ this . cachedBytes - removedBytes - bytes < maxBytesSize &&
301+ itemList . length - removedNodes <= maxSize
302+ ) {
248303
249- // note that these conditions ensure we keep one tile over the byte cap so we can
250- // align with the the isFull function reports.
304+ break ;
305+
306+ }
307+
308+ removedBytes += bytes ;
309+ removedNodes ++ ;
310+
311+ }
251312
252- // base while condition
253- const doContinue =
254- removedNodes < nodesToUnload
255- || removedBytes < bytesToUnload
256- || this . cachedBytes - removedBytes - bytes > maxBytesSize
257- || itemList . length - removedNodes > maxSize ;
313+ // evict up to the min node or bytes size, keeping one more item over the min bytes limit
314+ // so we're meeting it
315+ while (
316+ removedBytes < bytesToUnload ||
317+ removedNodes < nodesToUnload
318+ ) {
258319
259- // don't unload any used tiles unless we're above our size cap
320+ const item = itemList [ removedNodes ] ;
321+ const bytes = bytesMap . get ( item ) || 0 ;
260322 if (
261- ! doContinue
262- || removedNodes >= unused
263- && this . cachedBytes - removedBytes - bytes <= maxBytesSize
264- && itemList . length - removedNodes <= maxSize
323+ usedSet . has ( item ) ||
324+ this . cachedBytes - removedBytes - bytes < minBytesSize &&
325+ removedNodes >= nodesToUnload
265326 ) {
266327
267328 break ;
@@ -271,15 +332,21 @@ class LRUCache {
271332 removedBytes += bytes ;
272333 removedNodes ++ ;
273334
274- bytesMap . delete ( item ) ;
335+ }
336+
337+ // remove the nodes
338+ itemList . splice ( 0 , removedNodes ) . forEach ( item => {
339+
340+ this . cachedBytes -= bytesMap . get ( item ) || 0 ;
341+
275342 callbacks . get ( item ) ( item ) ;
343+ bytesMap . delete ( item ) ;
276344 itemSet . delete ( item ) ;
277345 callbacks . delete ( item ) ;
346+ loadedSet . delete ( item ) ;
347+ usedSet . delete ( item ) ;
278348
279- }
280-
281- itemList . splice ( 0 , removedNodes ) ;
282- this . cachedBytes -= removedBytes ;
349+ } ) ;
283350
284351 // if we didn't remove enough nodes or we still have excess bytes and there are nodes to removed
285352 // then we want to fire another round of unloading
0 commit comments