own-tiles-renderer2
Version:
https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification
193 lines (122 loc) • 3.89 kB
JavaScript
// Fires at the end of the frame and before the next one
function enqueueMicrotask( callback ) {
Promise.resolve().then( callback );
}
class LRUCache {
constructor() {
// options
this.maxSize = 800;
this.minSize = 600;
this.unloadPercent = 0.05;
// "itemSet" doubles as both the list of the full set of items currently
// stored in the cache (keys) as well as a map to the time the item was last
// used so it can be sorted appropriately.
this.itemSet = new Map();
this.itemList = [];
this.usedSet = new Set();
this.unloadPriorityCallback = null;
const itemSet = this.itemSet;
this.defaultPriorityCallback = item => itemSet.get( item );
}
// Returns whether or not the cache has reached the maximum size
isFull() {
return this.itemSet.size >= this.maxSize;
}
unloadTile() {
}
add( item, removeCb ) {
const itemSet = this.itemSet;
if ( itemSet.has( item ) ) {
return false;
}
if ( this.isFull() ) {
return false;
}
const usedSet = this.usedSet;
const itemList = this.itemList;
itemList.push( item );
usedSet.add( item );
itemSet.set( item, Date.now() );
return true;
}
remove( item ) {
const usedSet = this.usedSet;
const itemSet = this.itemSet;
const itemList = this.itemList;
if ( itemSet.has( item ) ) {
this.unloadTile( item );
const index = itemList.indexOf( item );
itemList.splice( index, 1 );
usedSet.delete( item );
itemSet.delete( item );
return true;
}
return false;
}
markUsed( item ) {
const itemSet = this.itemSet;
const usedSet = this.usedSet;
if ( itemSet.has( item ) && ! usedSet.has( item ) ) {
itemSet.set( item, Date.now() );
usedSet.add( item );
}
}
markAllUnused() {
this.usedSet.clear();
}
// TODO: this should be renamed because it's not necessarily unloading all unused content
// Maybe call it "cleanup" or "unloadToMinSize"
unloadUnusedContent() {
const unloadPercent = this.unloadPercent;
const targetSize = this.minSize;
const itemList = this.itemList;
const itemSet = this.itemSet;
const usedSet = this.usedSet;
const unused = itemList.length - usedSet.size;
const excess = itemList.length - targetSize;
const unloadPriorityCallback = this.unloadPriorityCallback || this.defaultPriorityCallback;
if ( excess > 0 && unused > 0 ) {
// used items should be at the end of the array
itemList.sort( ( a, b ) => {
const usedA = usedSet.has( a );
const usedB = usedSet.has( b );
if ( usedA && usedB ) {
// If they're both used then don't bother moving them
return 0;
} else if ( ! usedA && ! usedB ) {
// Use the sort function otherwise
// higher priority should be further to the left
return unloadPriorityCallback( b ) - unloadPriorityCallback( a );
} else {
// If one is used and the other is not move the used one towards the end of the array
return usedA ? 1 : - 1;
}
} );
// address corner cases where the minSize might be zero or smaller than maxSize - minSize,
// which would result in a very small or no items being unloaded.
const unusedExcess = Math.min( excess, unused );
const maxUnload = Math.max( targetSize * unloadPercent, unusedExcess * unloadPercent );
let nodesToUnload = Math.min( maxUnload, unused );
nodesToUnload = Math.ceil( nodesToUnload );
const removedItems = itemList.splice( 0, nodesToUnload );
for ( let i = 0, l = removedItems.length; i < l; i ++ ) {
const item = removedItems[ i ];
itemSet.delete( item );
this.unloadTile( item );
}
}
}
scheduleUnload( markAllUnused = true ) {
if ( ! this.scheduled ) {
this.scheduled = true;
enqueueMicrotask( () => {
this.scheduled = false;
this.unloadUnusedContent();
if ( markAllUnused ) {
this.markAllUnused();
}
} );
}
}
}
export { LRUCache };