3d-tiles-renderer
Version:
https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification
245 lines (154 loc) • 4.34 kB
JavaScript
function hash( ...args ) {
return args.join( '_' );
}
// class for retrieving and locking data being requested
// "fetchItem" and "disposeItem" should be implemented
export class DataCache {
constructor() {
this.cache = {};
this.count = 0;
this.cachedBytes = 0;
this.active = 0;
}
// overridable
fetchItem() {}
disposeItem() {}
getMemoryUsage( item ) {
return 0;
}
// sets the data in the cache explicitly without need to load
setData( ...args ) {
const { cache } = this;
const data = args.pop();
const key = hash( ...args );
if ( key in cache ) {
throw new Error( `DataCache: "${ key }" is already present.` );
} else {
this.cache[ key ] = {
abortController: new AbortController(),
result: data,
count: 1,
bytes: this.getMemoryUsage( data ),
};
this.count ++;
this.cachedBytes += this.cache[ key ].bytes;
}
return data;
}
// fetches the associated data if it doesn't exist and increments the lock counter
lock( ...args ) {
const { cache } = this;
const key = hash( ...args );
if ( key in cache ) {
cache[ key ].count ++;
} else {
const abortController = new AbortController();
const info = {
abortController,
result: null,
count: 1,
bytes: 0,
};
this.active ++;
info.result = this.fetchItem( args, abortController.signal );
if ( info.result instanceof Promise ) {
info.result.then( res => {
info.result = res;
info.bytes = this.getMemoryUsage( res );
this.cachedBytes += info.bytes;
return res;
} ).finally( () => {
this.active --;
} ).catch( e => {
// error logging and handling can be handled elsewhere
} );
} else {
this.active --;
info.bytes = this.getMemoryUsage( info.result );
this.cachedBytes += info.bytes;
}
this.cache[ key ] = info;
this.count ++;
}
return cache[ key ].result;
}
// decrements the lock counter for the item and deletes the item if it has reached zero
release( ...args ) {
const key = hash( ...args );
this.releaseViaFullKey( key );
}
// get the loaded item
get( ...args ) {
const { cache } = this;
const key = hash( ...args );
if ( key in cache && cache[ key ].count > 0 ) {
return cache[ key ].result;
} else {
return null;
}
}
has( ...args ) {
const { cache } = this;
const key = hash( ...args );
return key in cache;
}
// dispose all items
dispose() {
const { cache } = this;
for ( const key in cache ) {
const { abortController } = cache[ key ];
abortController.abort();
this.releaseViaFullKey( key, true );
}
this.cache = {};
}
// releases an item with an optional force flag
releaseViaFullKey( key, force = false ) {
const { cache } = this;
if ( key in cache && cache[ key ].count > 0 ) {
// decrement the lock
const info = cache[ key ];
info.count --;
// if the item is no longer being used
if ( info.count === 0 || force ) {
const disposeCallback = () => {
// if the object isn't in the cache anymore then exit early because it's been disposed elsewhere
if ( cache[ key ] !== info ) {
return;
}
// abort any loads
const { result, abortController } = info;
abortController.abort();
// dispose of the object even if it still is in progress
if ( result instanceof Promise ) {
// "disposeItem" will throw potentially if fetch, etc are cancelled using the abort signal
result.then( item => {
this.disposeItem( item );
this.count --;
this.cachedBytes -= info.bytes;
} ).catch( () => {} );
} else {
this.disposeItem( result );
this.count --;
this.cachedBytes -= info.bytes;
}
delete cache[ key ];
};
if ( force ) {
// if we're forcing disposal then dispose immediately
disposeCallback();
} else {
// queue for disposal in a frame here - we need to make sure we're not disposing of something twice
// this can get called multiple times in a row to increment then decrement again.
queueMicrotask( () => {
if ( info.count === 0 ) {
disposeCallback();
}
} );
}
}
return true;
}
throw new Error( 'DataCache: Attempting to release key that does not exist' );
}
}