Feature/Optimization: cache can be created by a callback (async or sync), to avoid premature data creation

This commit is contained in:
Aiosa 2024-10-16 16:31:08 +02:00
parent b6693ee50d
commit f8e5cff117
2 changed files with 64 additions and 30 deletions

View File

@ -423,8 +423,9 @@ $.Tile.prototype = {
* @deprecated
*/
set context2D(value) {
$.console.error("[Tile.context2D] property has been deprecated. Use [Tile.setData] instead.");
$.console.error("[Tile.context2D] property has been deprecated. Use [Tile.setData] within dedicated update event instead.");
this.setData(value, "context2d");
this.updateRenderTarget();
},
/**
@ -473,26 +474,7 @@ $.Tile.prototype = {
if (!this.tiledImage) {
return $.Promise.resolve(); //async can access outside its lifetime
}
$.console.assert("TIle.getData requires type argument! got '%s'.", type);
//we return the data synchronously immediatelly (undefined if conversion happens)
const cache = this.getCache(this._wcKey);
if (!cache) {
const targetCopyKey = this.__restore ? this.originalCacheKey : this.cacheKey;
const origCache = this.getCache(targetCopyKey);
if (!origCache) {
$.console.error("[Tile::getData] There is no cache available for tile with key %s", targetCopyKey);
}
//todo consider calling addCache with callback, which can avoid creating data item only to just discard it
// in case we addCache with existing key and the current tile just gets attached as a reference
// .. or explicitly check that such cache does not exist globally (now checking only locally)
return origCache.getDataAs(type, true).then(data => {
return this.addCache(this._wcKey, data, type, false, false).await();
});
}
return cache.getDataAs(type, false);
return this._getOrCreateWorkingCacheData(type);
},
/**
@ -521,10 +503,10 @@ $.Tile.prototype = {
return null; //async context can access the tile outside its lifetime
}
const cache = this.getCache(this._wcKey);
let cache = this.getCache(this._wcKey);
if (!cache) {
$.console.error("[Tile::setData] You cannot set data without calling tile.getData()! The working cache is not initialized!");
return $.Promise.resolve();
this._getOrCreateWorkingCacheData(undefined);
cache = this.getCache(this._wcKey);
}
return cache.setDataAs(value, type);
},
@ -611,8 +593,11 @@ $.Tile.prototype = {
* @param {string} key cache key, must be unique (we recommend re-using this.cacheTile
* value and extend it with some another unique content, by default overrides the existing
* main cache used for drawing, if not existing.
* @param {*} data data to cache - this data will be IGNORED if cache already exists!
* @param {string} [type=undefined] data type, will be guessed if not provided
* @param {*} data this data will be IGNORED if cache already exists; therefore if
* `typeof data === 'function'` holds (both async and normal functions), the data is called to obtain
* the data item: this is an optimization to load data only when necessary.
* @param {string} [type=undefined] data type, will be guessed if not provided (not recommended),
* if data is a callback the type is a mandatory field, not setting it results in undefined behaviour
* @param {boolean} [setAsMain=false] if true, the key will be set as the tile.cacheKey
* @param [_safely=true] private
* @returns {OpenSeadragon.CacheRecord|null} - The cache record the tile was attached to.
@ -628,6 +613,9 @@ $.Tile.prototype = {
"Automated deduction is potentially unsafe: prefer specification of data type explicitly.");
this.__typeWarningReported = true;
}
if (typeof data === 'function') {
$.console.error("[TileCache.cacheTile] options.data as a callback requires type argument! Current is " + type);
}
type = $.convertor.guessType(data);
}
@ -677,6 +665,30 @@ $.Tile.prototype = {
// as drawers request data for drawing
},
/**
* Initializes working cache if it does not exist.
* @param {string|undefined} type initial cache type to create
* @return {OpenSeadragon.Promise<?>} data-awaiting promise with the cache data
* @private
*/
_getOrCreateWorkingCacheData: function (type) {
const cache = this.getCache(this._wcKey);
if (!cache) {
const targetCopyKey = this.__restore ? this.originalCacheKey : this.cacheKey;
const origCache = this.getCache(targetCopyKey);
if (!origCache) {
$.console.error("[Tile::getData] There is no cache available for tile with key %s", targetCopyKey);
}
// Here ensure type is defined, rquired by data callbacks
type = type || origCache.type;
// Here we use extensively ability to call addCache with callback: working cache is created only if not
// already in memory (=> shared).
return this.addCache(this._wcKey, () => origCache.getDataAs(type, true), type, false, false).await();
}
return cache.getDataAs(type, false);
},
/**
* Get the number of caches available to this tile
* @returns {number} number of caches

View File

@ -75,7 +75,7 @@
/**
* Await ongoing process so that we get cache ready on callback.
* @returns {Promise<any>}
* @returns {OpenSeadragon.Promise<?>}
*/
await() {
if (!this._promise) { //if not cache loaded, do not fail
@ -403,9 +403,24 @@
// first come first served, data for existing tiles is NOT overridden
if (this._tiles.length < 1) {
// Since we IGNORE new data if already initialized, we support 'data getter'
if (typeof data === 'function') {
data = data();
}
// If we receive async callback, we consume the async state
if (data instanceof $.Promise) {
this._promise = data.then(d => {
this._data = d;
return d;
});
this._data = null;
} else {
this._promise = $.Promise.resolve(data);
this._data = data;
}
this._type = type;
this._promise = $.Promise.resolve(data);
this._data = data;
this.loaded = true;
this._tiles.push(tile);
} else if (!this._tiles.includes(tile)) {
@ -734,7 +749,8 @@
* @param {String} options.tile.cacheKey - The unique key used to identify this tile in the cache.
* Used if options.cacheKey not set.
* @param {Image} options.image - The image of the tile to cache. Deprecated.
* @param {*} options.data - The data of the tile to cache.
* @param {*} options.data - The data of the tile to cache. If `typeof data === 'function'` holds,
* the data is called to obtain the data item: this is an optimization to load data only when necessary.
* @param {string} [options.dataType] - The data type of the tile to cache. Required.
* @param {Number} [options.cutoff=0] - If adding this tile goes over the cache max count, this
* function will release an old tile. The cutoff option specifies a tile level at or below which
@ -777,6 +793,12 @@
if (!options.dataType) {
$.console.error("[TileCache.cacheTile] options.dataType is newly required. " +
"For easier use of the cache system, use the tile instance API.");
// We need to force data acquisition now to guess the type
if (typeof options.data === 'function') {
$.console.error("[TileCache.cacheTile] options.dataType is mandatory " +
" when data item is a callback!");
}
options.dataType = $.convertor.guessType(options.data);
}