mirror of
https://github.com/openseadragon/openseadragon.git
synced 2024-11-29 00:26:10 +03:00
Redesign working cache: it is now owned by the event, not a tile. Tests are not yet updated.
This commit is contained in:
parent
e059b8982e
commit
541fe2e4df
280
src/tile.js
280
src/tile.js
@ -33,7 +33,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
(function( $ ){
|
(function( $ ){
|
||||||
let _workingCacheIdDealer = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Tile
|
* @class Tile
|
||||||
@ -252,16 +251,6 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this._caches = {};
|
this._caches = {};
|
||||||
/**
|
|
||||||
* Static Working Cache key to keep cached object (for swapping) when executing modifications.
|
|
||||||
* Uses unique ID to prevent sharing between other tiles:
|
|
||||||
* - if some tile initiates processing, all other tiles usually are skipped if they share the data
|
|
||||||
* - if someone tries to bypass sharing and process all tiles that share data, working caches would collide
|
|
||||||
* Note that $.now() is not sufficient, there might be tile created in the same millisecond.
|
|
||||||
* @member {String}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this._wcKey = `w${_workingCacheIdDealer++}://` + this.originalCacheKey;
|
|
||||||
/**
|
/**
|
||||||
* Processing flag, exempt the tile from removal when there are ongoing updates
|
* Processing flag, exempt the tile from removal when there are ongoing updates
|
||||||
* @member {Boolean|Number}
|
* @member {Boolean|Number}
|
||||||
@ -273,14 +262,6 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.lastProcess = 0;
|
this.lastProcess = 0;
|
||||||
/**
|
|
||||||
* Transforming flag, exempt the tile from any processing since it is being transformed to a drawer-compatible
|
|
||||||
* format. This process cannot be paused and the tile cannot be touched during the process. Used for tile-locking
|
|
||||||
* in the data invalidation routine.
|
|
||||||
* @member {Boolean|Number}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this.transforming = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @lends OpenSeadragon.Tile.prototype */
|
/** @lends OpenSeadragon.Tile.prototype */
|
||||||
@ -420,7 +401,7 @@ $.Tile.prototype = {
|
|||||||
* @returns {?Image}
|
* @returns {?Image}
|
||||||
*/
|
*/
|
||||||
getImage: function() {
|
getImage: function() {
|
||||||
$.console.error("[Tile.getImage] property has been deprecated. Use [Tile.getData] instead.");
|
$.console.error("[Tile.getImage] property has been deprecated. Use 'tile-invalidated' routine event instead.");
|
||||||
//this method used to ensure the underlying data model conformed to given type - convert instead of getData()
|
//this method used to ensure the underlying data model conformed to given type - convert instead of getData()
|
||||||
const cache = this.getCache(this.cacheKey);
|
const cache = this.getCache(this.cacheKey);
|
||||||
if (!cache) {
|
if (!cache) {
|
||||||
@ -445,10 +426,10 @@ $.Tile.prototype = {
|
|||||||
/**
|
/**
|
||||||
* Get the CanvasRenderingContext2D instance for tile image data drawn
|
* Get the CanvasRenderingContext2D instance for tile image data drawn
|
||||||
* onto Canvas if enabled and available
|
* onto Canvas if enabled and available
|
||||||
* @returns {?CanvasRenderingContext2D}
|
* @returns {CanvasRenderingContext2D|undefined}
|
||||||
*/
|
*/
|
||||||
getCanvasContext: function() {
|
getCanvasContext: function() {
|
||||||
$.console.error("[Tile.getCanvasContext] property has been deprecated. Use [Tile.getData] instead.");
|
$.console.error("[Tile.getCanvasContext] property has been deprecated. Use 'tile-invalidated' routine event instead.");
|
||||||
//this method used to ensure the underlying data model conformed to given type - convert instead of getData()
|
//this method used to ensure the underlying data model conformed to given type - convert instead of getData()
|
||||||
const cache = this.getCache(this.cacheKey);
|
const cache = this.getCache(this.cacheKey);
|
||||||
if (!cache) {
|
if (!cache) {
|
||||||
@ -464,7 +445,7 @@ $.Tile.prototype = {
|
|||||||
* @type {CanvasRenderingContext2D}
|
* @type {CanvasRenderingContext2D}
|
||||||
*/
|
*/
|
||||||
get context2D() {
|
get context2D() {
|
||||||
$.console.error("[Tile.context2D] property has been deprecated. Use [Tile.getData] instead.");
|
$.console.error("[Tile.context2D] property has been deprecated. Use 'tile-invalidated' routine event instead.");
|
||||||
return this.getCanvasContext();
|
return this.getCanvasContext();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -473,9 +454,12 @@ $.Tile.prototype = {
|
|||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
set context2D(value) {
|
set context2D(value) {
|
||||||
$.console.error("[Tile.context2D] property has been deprecated. Use [Tile.setData] within dedicated update event instead.");
|
$.console.error("[Tile.context2D] property has been deprecated. Use 'tile-invalidated' routine event instead.");
|
||||||
this.setData(value, "context2d");
|
const cache = this._caches[this.cacheKey];
|
||||||
this.updateRenderTarget();
|
if (cache) {
|
||||||
|
this.removeCache(this.cacheKey);
|
||||||
|
}
|
||||||
|
this.addCache(this.cacheKey, value, 'context2d', true, false);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -510,128 +494,12 @@ $.Tile.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the data to render for this tile. If no conversion is necessary, get a reference. Else, get a copy
|
* Cache key for main cache that is 'cache-equal', but different from original cache key
|
||||||
* of the data as desired type. This means that data modification _might_ be reflected on the tile, but
|
* @return {string}
|
||||||
* it is not guaranteed. Use tile.setData() to ensure changes are reflected.
|
|
||||||
* @param {string} type data type to require
|
|
||||||
* @return {OpenSeadragon.Promise<*>} data in the desired type, or resolved promise with udnefined if the
|
|
||||||
* associated cache object is out of its lifespan
|
|
||||||
*/
|
|
||||||
getData: function(type) {
|
|
||||||
if (!this.tiledImage) {
|
|
||||||
return $.Promise.resolve(); //async can access outside its lifetime
|
|
||||||
}
|
|
||||||
return this._getOrCreateWorkingCacheData(type);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restore the original data data for this tile
|
|
||||||
* @param {boolean} freeIfUnused if true, restoration frees cache along the way of the tile lifecycle
|
|
||||||
*/
|
|
||||||
restore: function(freeIfUnused = true) {
|
|
||||||
if (!this.tiledImage) {
|
|
||||||
return; //async context can access the tile outside its lifetime
|
|
||||||
}
|
|
||||||
|
|
||||||
this.__restoreRequestedFree = freeIfUnused;
|
|
||||||
if (this.originalCacheKey !== this.cacheKey) {
|
|
||||||
this.__restore = true;
|
|
||||||
}
|
|
||||||
// Somebody has called restore on this tile, make sure we delete working cache in case there was some
|
|
||||||
this.removeCache(this._wcKey, true);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set main cache data
|
|
||||||
* @param {*} value
|
|
||||||
* @param {?string} type data type to require
|
|
||||||
* @return {OpenSeadragon.Promise<*>}
|
|
||||||
*/
|
|
||||||
setData: function(value, type) {
|
|
||||||
if (!this.tiledImage) {
|
|
||||||
return Promise.resolve(); //async context can access the tile outside its lifetime
|
|
||||||
}
|
|
||||||
|
|
||||||
let cache = this.getCache(this._wcKey);
|
|
||||||
if (!cache) {
|
|
||||||
this._getOrCreateWorkingCacheData(undefined);
|
|
||||||
cache = this.getCache(this._wcKey);
|
|
||||||
}
|
|
||||||
return cache.setDataAs(value, type);
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optimizazion: prepare target cache for subsequent use in rendering, and perform updateRenderTarget()
|
|
||||||
* The main idea of this function is that it must be ASYNCHRONOUS since there might be additional processing
|
|
||||||
* happening due to underlying drawer requirements.
|
|
||||||
* @return {OpenSeadragon.Promise<?>}
|
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
updateRenderTargetWithDataTransform: function (drawerId, supportedFormats, usePrivateCache, processTimestamp) {
|
buildDistinctMainCacheKey: function () {
|
||||||
// Now, if working cache exists, we set main cache to the working cache --> prepare
|
return this.cacheKey === this.originalCacheKey ? "mod://" + this.originalCacheKey : this.cacheKey;
|
||||||
let cache = this.getCache(this._wcKey);
|
|
||||||
if (cache) {
|
|
||||||
return cache.prepareForRendering(drawerId, supportedFormats, usePrivateCache).then(c => {
|
|
||||||
if (c && processTimestamp && this.processing === processTimestamp) {
|
|
||||||
this.updateRenderTarget();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we requested restore, perform now
|
|
||||||
if (this.__restore) {
|
|
||||||
cache = this.getCache(this.originalCacheKey);
|
|
||||||
|
|
||||||
this.tiledImage._tileCache.restoreTilesThatShareOriginalCache(
|
|
||||||
this, cache, this.__restoreRequestedFree
|
|
||||||
);
|
|
||||||
this.__restore = false;
|
|
||||||
return cache.prepareForRendering(drawerId, supportedFormats, usePrivateCache).then((c) => {
|
|
||||||
if (c && processTimestamp && this.processing === processTimestamp) {
|
|
||||||
this.updateRenderTarget();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cache = this.getCache();
|
|
||||||
return cache.prepareForRendering(drawerId, supportedFormats, usePrivateCache);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves render target: changes might've been made to the rendering pipeline:
|
|
||||||
* - working cache is set: make sure main cache will be replaced
|
|
||||||
* - working cache is unset: make sure main cache either gets updated to original data or stays (based on this.__restore)
|
|
||||||
*
|
|
||||||
* The main idea of this function is that it is SYNCHRONOUS, e.g. can perform in-place cache swap to update
|
|
||||||
* before any rendering occurs.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
updateRenderTarget: function (_allowTileNotLoaded = false) {
|
|
||||||
// Check if we asked for restore, and make sure we set it to false since we update the whole cache state
|
|
||||||
const requestedRestore = this.__restore;
|
|
||||||
this.__restore = false;
|
|
||||||
|
|
||||||
// Now, if working cache exists, we set main cache to the working cache, since it has been updated
|
|
||||||
// if restore() was called last, then working cache was deleted (does not exist)
|
|
||||||
const cache = this.getCache(this._wcKey);
|
|
||||||
if (cache) {
|
|
||||||
let newCacheKey = this.cacheKey === this.originalCacheKey ? "mod://" + this.originalCacheKey : this.cacheKey;
|
|
||||||
this.tiledImage._tileCache.consumeCache({
|
|
||||||
tile: this,
|
|
||||||
victimKey: this._wcKey,
|
|
||||||
consumerKey: newCacheKey,
|
|
||||||
tileAllowNotLoaded: _allowTileNotLoaded
|
|
||||||
});
|
|
||||||
this.cacheKey = newCacheKey;
|
|
||||||
} else if (requestedRestore) {
|
|
||||||
// If we requested restore, perform now
|
|
||||||
this.tiledImage._tileCache.restoreTilesThatShareOriginalCache(
|
|
||||||
this, this.getCache(this.originalCacheKey), this.__restoreRequestedFree
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// If transforming was set, we finished: drawer transform always finishes with updateRenderTarget()
|
|
||||||
this.transforming = false;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -648,10 +516,10 @@ $.Tile.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set tile cache, possibly multiple with custom key
|
* Create tile cache for given data object. NOTE: if the existing cache already exists,
|
||||||
* @param {string} key cache key, must be unique (we recommend re-using this.cacheTile
|
* data parameter is ignored and inherited from the existing cache object.
|
||||||
* value and extend it with some another unique content, by default overrides the existing
|
*
|
||||||
* main cache used for drawing, if not existing.
|
* @param {string} key cache key, if unique, new cache object is created, else existing cache attached
|
||||||
* @param {*} data this data will be IGNORED if cache already exists; therefore if
|
* @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
|
* `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.
|
* the data item: this is an optimization to load data only when necessary.
|
||||||
@ -663,7 +531,8 @@ $.Tile.prototype = {
|
|||||||
* @returns {OpenSeadragon.CacheRecord|null} - The cache record the tile was attached to.
|
* @returns {OpenSeadragon.CacheRecord|null} - The cache record the tile was attached to.
|
||||||
*/
|
*/
|
||||||
addCache: function(key, data, type = undefined, setAsMain = false, _safely = true) {
|
addCache: function(key, data, type = undefined, setAsMain = false, _safely = true) {
|
||||||
if (!this.tiledImage) {
|
const tiledImage = this.tiledImage;
|
||||||
|
if (!tiledImage) {
|
||||||
return null; //async can access outside its lifetime
|
return null; //async can access outside its lifetime
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -679,35 +548,83 @@ $.Tile.prototype = {
|
|||||||
type = $.convertor.guessType(data);
|
type = $.convertor.guessType(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
const writesToRenderingCache = key === this.cacheKey;
|
const overwritesMainCache = key === this.cacheKey;
|
||||||
if (writesToRenderingCache && _safely) {
|
if (_safely && (overwritesMainCache || setAsMain)) {
|
||||||
// Need to get the supported type for rendering out of the active drawer.
|
// Need to get the supported type for rendering out of the active drawer.
|
||||||
const supportedTypes = this.tiledImage.viewer.drawer.getSupportedDataFormats();
|
const supportedTypes = tiledImage.viewer.drawer.getSupportedDataFormats();
|
||||||
const conversion = $.convertor.getConversionPath(type, supportedTypes);
|
const conversion = $.convertor.getConversionPath(type, supportedTypes);
|
||||||
$.console.assert(conversion, "[Tile.addCache] data was set for the default tile cache we are unable" +
|
$.console.assert(conversion, "[Tile.addCache] data was set for the default tile cache we are unable" +
|
||||||
"to render. Make sure OpenSeadragon.convertor was taught to convert to (one of): " + type);
|
`to render. Make sure OpenSeadragon.convertor was taught to convert ${type} to (one of): ${conversion.toString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cachedItem = this.tiledImage._tileCache.cacheTile({
|
const cachedItem = tiledImage._tileCache.cacheTile({
|
||||||
data: data,
|
data: data,
|
||||||
dataType: type,
|
dataType: type,
|
||||||
tile: this,
|
tile: this,
|
||||||
cacheKey: key,
|
cacheKey: key,
|
||||||
//todo consider caching this on a tiled image level
|
cutoff: tiledImage.source.getClosestLevel(),
|
||||||
cutoff: this.__cutoff || this.tiledImage.source.getClosestLevel(),
|
|
||||||
});
|
});
|
||||||
const havingRecord = this._caches[key];
|
const havingRecord = this._caches[key];
|
||||||
if (havingRecord !== cachedItem) {
|
if (havingRecord !== cachedItem) {
|
||||||
this._caches[key] = cachedItem;
|
this._caches[key] = cachedItem;
|
||||||
|
if (havingRecord) {
|
||||||
|
havingRecord.removeTile(this);
|
||||||
|
tiledImage._tileCache.safeUnloadCache(havingRecord);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update cache key if differs and main requested
|
// Update cache key if differs and main requested
|
||||||
if (!writesToRenderingCache && setAsMain) {
|
if (!overwritesMainCache && setAsMain) {
|
||||||
this._updateMainCacheKey(key);
|
this._updateMainCacheKey(key);
|
||||||
}
|
}
|
||||||
return cachedItem;
|
return cachedItem;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add cache object to the tile
|
||||||
|
*
|
||||||
|
* @param {string} key cache key, if unique, new cache object is created, else existing cache attached
|
||||||
|
* @param {OpenSeadragon.CacheRecord} cache the cache object to attach to this tile
|
||||||
|
* @param {boolean} [setAsMain=false] if true, the key will be set as the tile.cacheKey,
|
||||||
|
* no effect if key === this.cacheKey
|
||||||
|
* @param [_safely=true] private
|
||||||
|
* @returns {OpenSeadragon.CacheRecord|null} - Returns cache parameter reference if attached.
|
||||||
|
*/
|
||||||
|
setCache(key, cache, setAsMain = false, _safely = true) {
|
||||||
|
const tiledImage = this.tiledImage;
|
||||||
|
if (!tiledImage) {
|
||||||
|
return null; //async can access outside its lifetime
|
||||||
|
}
|
||||||
|
|
||||||
|
const overwritesMainCache = key === this.cacheKey;
|
||||||
|
if (_safely) {
|
||||||
|
$.console.assert(cache instanceof $.CacheRecord, "[Tile.setCache] cache must be a CacheRecord object!");
|
||||||
|
if (overwritesMainCache || setAsMain) {
|
||||||
|
// Need to get the supported type for rendering out of the active drawer.
|
||||||
|
const supportedTypes = tiledImage.viewer.drawer.getSupportedDataFormats();
|
||||||
|
const conversion = $.convertor.getConversionPath(cache.type, supportedTypes);
|
||||||
|
$.console.assert(conversion, "[Tile.setCache] data was set for the default tile cache we are unable" +
|
||||||
|
`to render. Make sure OpenSeadragon.convertor was taught to convert ${cache.type} to (one of): ${conversion.toString()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const havingRecord = this._caches[key];
|
||||||
|
if (havingRecord !== cache) {
|
||||||
|
this._caches[key] = cache;
|
||||||
|
if (havingRecord) {
|
||||||
|
havingRecord.removeTile(this);
|
||||||
|
tiledImage._tileCache.safeUnloadCache(havingRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cache key if differs and main requested
|
||||||
|
if (!overwritesMainCache && setAsMain) {
|
||||||
|
this._updateMainCacheKey(key);
|
||||||
|
}
|
||||||
|
return cache;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the main cache key for this tile and
|
* Sets the main cache key for this tile and
|
||||||
* performs necessary updates
|
* performs necessary updates
|
||||||
@ -717,36 +634,11 @@ $.Tile.prototype = {
|
|||||||
_updateMainCacheKey: function(value) {
|
_updateMainCacheKey: function(value) {
|
||||||
let ref = this._caches[this._cKey];
|
let ref = this._caches[this._cKey];
|
||||||
if (ref) {
|
if (ref) {
|
||||||
// make sure we free drawer internal cache
|
// make sure we free drawer internal cache if people change cache key externally
|
||||||
|
// todo make sure this is really needed even after refactoring
|
||||||
ref.destroyInternalCache();
|
ref.destroyInternalCache();
|
||||||
}
|
}
|
||||||
this._cKey = value;
|
this._cKey = value;
|
||||||
// we do not trigger redraw, this is handled within cache
|
|
||||||
// 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);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -761,12 +653,15 @@ $.Tile.prototype = {
|
|||||||
* Free tile cache. Removes by default the cache record if no other tile uses it.
|
* Free tile cache. Removes by default the cache record if no other tile uses it.
|
||||||
* @param {string} key cache key, required
|
* @param {string} key cache key, required
|
||||||
* @param {boolean} [freeIfUnused=true] set to false if zombie should be created
|
* @param {boolean} [freeIfUnused=true] set to false if zombie should be created
|
||||||
|
* @return {OpenSeadragon.CacheRecord|undefined} reference to the cache record if it was removed,
|
||||||
|
* undefined if removal was refused to perform (e.g. does not exist, it is an original data target etc.)
|
||||||
*/
|
*/
|
||||||
removeCache: function(key, freeIfUnused = true) {
|
removeCache: function(key, freeIfUnused = true) {
|
||||||
if (!this._caches[key]) {
|
const deleteTarget = this._caches[key];
|
||||||
|
if (!deleteTarget) {
|
||||||
// try to erase anyway in case the cache got stuck in memory
|
// try to erase anyway in case the cache got stuck in memory
|
||||||
this.tiledImage._tileCache.unloadCacheForTile(this, key, freeIfUnused, true);
|
this.tiledImage._tileCache.unloadCacheForTile(this, key, freeIfUnused, true);
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentMainKey = this.cacheKey,
|
const currentMainKey = this.cacheKey,
|
||||||
@ -776,7 +671,7 @@ $.Tile.prototype = {
|
|||||||
if (!sameBuiltinKeys && originalDataKey === key) {
|
if (!sameBuiltinKeys && originalDataKey === key) {
|
||||||
$.console.warn("[Tile.removeCache] original data must not be manually deleted: other parts of the code might rely on it!",
|
$.console.warn("[Tile.removeCache] original data must not be manually deleted: other parts of the code might rely on it!",
|
||||||
"If you want the tile not to preserve the original data, toggle of data perseverance in tile.setData().");
|
"If you want the tile not to preserve the original data, toggle of data perseverance in tile.setData().");
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentMainKey === key) {
|
if (currentMainKey === key) {
|
||||||
@ -786,13 +681,14 @@ $.Tile.prototype = {
|
|||||||
} else {
|
} else {
|
||||||
$.console.warn("[Tile.removeCache] trying to remove the only cache that can be used to draw the tile!",
|
$.console.warn("[Tile.removeCache] trying to remove the only cache that can be used to draw the tile!",
|
||||||
"If you want to remove the main cache, first set different cache as main with tile.addCache()");
|
"If you want to remove the main cache, first set different cache as main with tile.addCache()");
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.tiledImage._tileCache.unloadCacheForTile(this, key, freeIfUnused, false)) {
|
if (this.tiledImage._tileCache.unloadCacheForTile(this, key, freeIfUnused, false)) {
|
||||||
//if we managed to free tile from record, we are sure we decreased cache count
|
//if we managed to free tile from record, we are sure we decreased cache count
|
||||||
delete this._caches[key];
|
delete this._caches[key];
|
||||||
}
|
}
|
||||||
|
return deleteTarget;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -826,8 +722,8 @@ $.Tile.prototype = {
|
|||||||
// the sketch canvas to the top and left and we must use negative coordinates to repaint it
|
// the sketch canvas to the top and left and we must use negative coordinates to repaint it
|
||||||
// to the main canvas. In that case, some browsers throw:
|
// to the main canvas. In that case, some browsers throw:
|
||||||
// INDEX_SIZE_ERR: DOM Exception 1: Index or size was negative, or greater than the allowed value.
|
// INDEX_SIZE_ERR: DOM Exception 1: Index or size was negative, or greater than the allowed value.
|
||||||
var x = Math.max(1, Math.ceil((sketchCanvasSize.x - canvasSize.x) / 2));
|
const x = Math.max(1, Math.ceil((sketchCanvasSize.x - canvasSize.x) / 2));
|
||||||
var y = Math.max(1, Math.ceil((sketchCanvasSize.y - canvasSize.y) / 2));
|
const y = Math.max(1, Math.ceil((sketchCanvasSize.y - canvasSize.y) / 2));
|
||||||
return new $.Point(x, y).minus(
|
return new $.Point(x, y).minus(
|
||||||
this.position
|
this.position
|
||||||
.times($.pixelDensityRatio)
|
.times($.pixelDensityRatio)
|
||||||
|
119
src/tilecache.js
119
src/tilecache.js
@ -163,7 +163,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Access of the data by drawers, synchronous function. Should always access a valid main cache, e.g.
|
* Access of the data by drawers, synchronous function. Should always access a valid main cache, e.g.
|
||||||
* cache swap performed on working cache (consumeCache()) must be synchronous such that cache is always
|
* cache swap performed on working cache (replaceCache()) must be synchronous such that cache is always
|
||||||
* ready to render, and swaps atomically between render calls.
|
* ready to render, and swaps atomically between render calls.
|
||||||
*
|
*
|
||||||
* @param {OpenSeadragon.DrawerBase} drawer drawer reference which requests the data: the drawer
|
* @param {OpenSeadragon.DrawerBase} drawer drawer reference which requests the data: the drawer
|
||||||
@ -190,8 +190,9 @@
|
|||||||
let internalCache = this[DRAWER_INTERNAL_CACHE];
|
let internalCache = this[DRAWER_INTERNAL_CACHE];
|
||||||
internalCache = internalCache && internalCache[drawer.getId()];
|
internalCache = internalCache && internalCache[drawer.getId()];
|
||||||
if (keepInternalCopy && !internalCache) {
|
if (keepInternalCopy && !internalCache) {
|
||||||
$.console.warn("Attempt to render %s that is not prepared with drawer requesting " +
|
$.console.warn("Attempt to render cache that is not prepared for current drawer " +
|
||||||
"internal cache! This might introduce artifacts.", this.toString());
|
"supported format: the preparation should've happened after tile processing has finished.",
|
||||||
|
this, tileToDraw);
|
||||||
|
|
||||||
this.prepareForRendering(drawer.getId(), supportedTypes, keepInternalCopy)
|
this.prepareForRendering(drawer.getId(), supportedTypes, keepInternalCopy)
|
||||||
.then(() => this._triggerNeedsDraw());
|
.then(() => this._triggerNeedsDraw());
|
||||||
@ -211,8 +212,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!supportedTypes.includes(internalCache.type)) {
|
if (!supportedTypes.includes(internalCache.type)) {
|
||||||
$.console.warn("Attempt to render %s that is not prepared for current drawer " +
|
$.console.warn("Attempt to render cache that is not prepared for current drawer " +
|
||||||
"supported format: the preparation should've happened after tile processing has finished.", this.toString());
|
"supported format: the preparation should've happened after tile processing has finished.",
|
||||||
|
Object.entries(this[DRAWER_INTERNAL_CACHE]),
|
||||||
|
this, tileToDraw);
|
||||||
|
|
||||||
internalCache.transformTo(supportedTypes.length > 1 ? supportedTypes : supportedTypes[0])
|
internalCache.transformTo(supportedTypes.length > 1 ? supportedTypes : supportedTypes[0])
|
||||||
.then(() => this._triggerNeedsDraw());
|
.then(() => this._triggerNeedsDraw());
|
||||||
@ -226,8 +229,8 @@
|
|||||||
* @private
|
* @private
|
||||||
* @param drawerId
|
* @param drawerId
|
||||||
* @param supportedTypes
|
* @param supportedTypes
|
||||||
* @param keepInternalCopy
|
* @param keepInternalCopy if a drawer requests internal copy, it means it can only use
|
||||||
|
* given cache for itself, cannot be shared -> initialize privately
|
||||||
* @return {OpenSeadragon.Promise<OpenSeadragon.SimpleCacheRecord|OpenSeadragon.CacheRecord> | null}
|
* @return {OpenSeadragon.Promise<OpenSeadragon.SimpleCacheRecord|OpenSeadragon.CacheRecord> | null}
|
||||||
* reference to the cache processed for drawer rendering requirements, or null on error
|
* reference to the cache processed for drawer rendering requirements, or null on error
|
||||||
*/
|
*/
|
||||||
@ -330,10 +333,12 @@
|
|||||||
* Conversion requires tile references:
|
* Conversion requires tile references:
|
||||||
* keep the most 'up to date' ref here. It is called and managed automatically.
|
* keep the most 'up to date' ref here. It is called and managed automatically.
|
||||||
* @param {OpenSeadragon.Tile} ref
|
* @param {OpenSeadragon.Tile} ref
|
||||||
|
* @return {OpenSeadragon.CacheRecord} self reference for builder pattern
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
withTileReference(ref) {
|
withTileReference(ref) {
|
||||||
this._tRef = ref;
|
this._tRef = ref;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -642,9 +647,11 @@
|
|||||||
* Must be called before transformTo or setDataAs. To keep
|
* Must be called before transformTo or setDataAs. To keep
|
||||||
* compatible api with CacheRecord where tile refs are known.
|
* compatible api with CacheRecord where tile refs are known.
|
||||||
* @param {OpenSeadragon.Tile} referenceTile reference tile for conversion
|
* @param {OpenSeadragon.Tile} referenceTile reference tile for conversion
|
||||||
|
* @return {OpenSeadragon.SimpleCacheRecord} self reference for builder pattern
|
||||||
*/
|
*/
|
||||||
withTileReference(referenceTile) {
|
withTileReference(referenceTile) {
|
||||||
this._temporaryTileRef = referenceTile;
|
this._temporaryTileRef = referenceTile;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -908,51 +915,92 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consume cache by another cache
|
* Inject new cache to the system
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {OpenSeadragon.Tile} options.tile - Reference tile. All tiles sharing original data will be affected.
|
||||||
|
* @param {OpenSeadragon.CacheRecord} options.cache - Cache that will be injected.
|
||||||
|
* @param {String} options.targetKey - The target cache key to inhabit. Can replace existing cache.
|
||||||
|
* @param {Boolean} options.setAsMainCache - If true, tiles main cache gets updated to consumerKey.
|
||||||
|
* Otherwise, if consumerKey==tile.cacheKey the cache is set as main too.
|
||||||
|
* @param {Boolean} options.tileAllowNotLoaded - if true, tile that is not loaded is also processed,
|
||||||
|
* this is internal parameter used in tile-loaded completion routine, as we need to prepare tile but
|
||||||
|
* it is not yet loaded and cannot be marked as so (otherwise the system would think it is ready)
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
injectCache(options) {
|
||||||
|
const targetKey = options.targetKey,
|
||||||
|
tile = options.tile;
|
||||||
|
if (!options.tileAllowNotLoaded && !tile.loaded && !tile.loading) {
|
||||||
|
$.console.warn("Attempt to inject cache on tile in invalid state: this is probably a bug!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const consumer = this._cachesLoaded[targetKey];
|
||||||
|
if (consumer) {
|
||||||
|
// We need to avoid async execution here: replace consumer instead of overwriting the data.
|
||||||
|
const iterateTiles = [...consumer._tiles]; // unloadCacheForTile() will modify the array, use a copy
|
||||||
|
for (let tile of iterateTiles) {
|
||||||
|
this.unloadCacheForTile(tile, targetKey, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this._cachesLoaded[targetKey]) {
|
||||||
|
$.console.error("The inject routine should've freed cache!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const cache = options.cache;
|
||||||
|
this._cachesLoaded[targetKey] = cache;
|
||||||
|
|
||||||
|
// Update cache: add the new cache, we must add since we removed above with unloadCacheForTile()
|
||||||
|
for (let t of tile.getCache(tile.originalCacheKey)._tiles) { // grab all cache-equal tiles
|
||||||
|
t.setCache(targetKey, cache, options.setAsMainCache, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace cache (and update tile references) by another cache
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
* @param {OpenSeadragon.Tile} options.tile - The tile to own ot add record for the cache.
|
* @param {OpenSeadragon.Tile} options.tile - The tile to own ot add record for the cache.
|
||||||
* @param {String} options.victimKey - Cache that will be erased. In fact, the victim _replaces_ consumer,
|
* @param {String} options.victimKey - Cache that will be erased. In fact, the victim _replaces_ consumer,
|
||||||
* inheriting its tiles and key.
|
* inheriting its tiles and key.
|
||||||
* @param {String} options.consumerKey - The cache that consumes the victim. In fact, it gets destroyed and
|
* @param {String} options.consumerKey - The cache that consumes the victim. In fact, it gets destroyed and
|
||||||
* replaced by victim, which inherits all its metadata.
|
* replaced by victim, which inherits all its metadata.
|
||||||
|
* @param {Boolean} options.setAsMainCache - If true, tiles main cache gets updated to consumerKey.
|
||||||
|
* Otherwise, if consumerKey==tile.cacheKey the cache is set as main too.
|
||||||
* @param {Boolean} options.tileAllowNotLoaded - if true, tile that is not loaded is also processed,
|
* @param {Boolean} options.tileAllowNotLoaded - if true, tile that is not loaded is also processed,
|
||||||
* this is internal parameter used in tile-loaded completion routine, as we need to prepare tile but
|
* this is internal parameter used in tile-loaded completion routine, as we need to prepare tile but
|
||||||
* it is not yet loaded and cannot be marked as so (otherwise the system would think it is ready)
|
* it is not yet loaded and cannot be marked as so (otherwise the system would think it is ready)
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
consumeCache(options) {
|
replaceCache(options) {
|
||||||
const victim = this._cachesLoaded[options.victimKey],
|
const victimKey = options.victimKey,
|
||||||
|
consumerKey = options.consumerKey,
|
||||||
|
victim = this._cachesLoaded[victimKey],
|
||||||
tile = options.tile;
|
tile = options.tile;
|
||||||
if (!victim || (!options.tileAllowNotLoaded && !tile.loaded && !tile.loading)) {
|
if (!victim || (!options.tileAllowNotLoaded && !tile.loaded && !tile.loading)) {
|
||||||
$.console.warn("Attempt to consume non-existent cache: this is probably a bug!");
|
$.console.warn("Attempt to consume cache on tile in invalid state: this is probably a bug!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const consumer = this._cachesLoaded[options.consumerKey];
|
const consumer = this._cachesLoaded[consumerKey];
|
||||||
let tiles = [...tile.getCache()._tiles];
|
|
||||||
|
|
||||||
if (consumer) {
|
if (consumer) {
|
||||||
// We need to avoid async execution here: replace consumer instead of overwriting the data.
|
// We need to avoid async execution here: replace consumer instead of overwriting the data.
|
||||||
const iterateTiles = [...consumer._tiles]; // unloadCacheForTile() will modify the array, use a copy
|
const iterateTiles = [...consumer._tiles]; // unloadCacheForTile() will modify the array, use a copy
|
||||||
for (let tile of iterateTiles) {
|
for (let tile of iterateTiles) {
|
||||||
this.unloadCacheForTile(tile, options.consumerKey, true, false);
|
this.unloadCacheForTile(tile, consumerKey, true, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this._cachesLoaded[options.consumerKey]) {
|
if (this._cachesLoaded[consumerKey]) {
|
||||||
console.error("The routine should've freed cache!");
|
$.console.error("The consume routine should've freed cache!");
|
||||||
}
|
}
|
||||||
// Just swap victim to become new consumer
|
// Just swap victim to become new consumer
|
||||||
const resultCache = this.renameCache({
|
const resultCache = this.renameCache({
|
||||||
oldCacheKey: options.victimKey,
|
oldCacheKey: victimKey,
|
||||||
newCacheKey: options.consumerKey
|
newCacheKey: consumerKey
|
||||||
});
|
});
|
||||||
|
|
||||||
if (resultCache) {
|
if (resultCache) {
|
||||||
// Only one cache got working item, other caches were idle: update cache: add the new cache
|
// Only one cache got working item, other caches were idle: update cache: add the new cache
|
||||||
// we can add since we removed above with unloadCacheForTile()
|
// we must add since we removed above with unloadCacheForTile()
|
||||||
for (let tile of tiles) {
|
for (let t of tile.getCache(tile.originalCacheKey)._tiles) { // grab all cache-equal tiles
|
||||||
if (tile !== options.tile) {
|
t.setCache(consumerKey, resultCache, options.setAsMainCache, false);
|
||||||
tile.addCache(options.consumerKey, resultCache.data, resultCache.type, true, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -967,11 +1015,13 @@
|
|||||||
*/
|
*/
|
||||||
restoreTilesThatShareOriginalCache(tile, originalCache, freeIfUnused) {
|
restoreTilesThatShareOriginalCache(tile, originalCache, freeIfUnused) {
|
||||||
for (let t of originalCache._tiles) {
|
for (let t of originalCache._tiles) {
|
||||||
this.unloadCacheForTile(t, t.cacheKey, freeIfUnused, false);
|
if (t.cacheKey !== t.originalCacheKey) {
|
||||||
|
this.unloadCacheForTile(t, t.cacheKey, freeIfUnused, true);
|
||||||
delete t._caches[t.cacheKey];
|
delete t._caches[t.cacheKey];
|
||||||
t.cacheKey = t.originalCacheKey;
|
t.cacheKey = t.originalCacheKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_freeOldRecordRoutine(theTile, cutoff) {
|
_freeOldRecordRoutine(theTile, cutoff) {
|
||||||
let insertionIndex = this._tilesLoaded.length,
|
let insertionIndex = this._tilesLoaded.length,
|
||||||
@ -1089,6 +1139,25 @@
|
|||||||
return this._cachesLoaded[cacheKey] || this._zombiesLoaded[cacheKey];
|
return this._cachesLoaded[cacheKey] || this._zombiesLoaded[cacheKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete cache safely from the system if it is not needed
|
||||||
|
* @param {OpenSeadragon.CacheRecord} cache
|
||||||
|
*/
|
||||||
|
safeUnloadCache(cache) {
|
||||||
|
if (cache && !cache._destroyed && cache.getTileCount() < 1) {
|
||||||
|
for (let i in this._zombiesLoaded) {
|
||||||
|
const c = this._zombiesLoaded[i];
|
||||||
|
if (c === cache) {
|
||||||
|
delete this._zombiesLoaded[i];
|
||||||
|
c.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$.console.error("Attempt to delete an orphan cache that is not in zombie list: this could be a bug!", cache);
|
||||||
|
cache.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete cache record for a given til
|
* Delete cache record for a given til
|
||||||
* @param {OpenSeadragon.Tile} tile
|
* @param {OpenSeadragon.Tile} tile
|
||||||
|
@ -1179,7 +1179,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
ajaxHeaders = {};
|
ajaxHeaders = {};
|
||||||
}
|
}
|
||||||
if (!$.isPlainObject(ajaxHeaders)) {
|
if (!$.isPlainObject(ajaxHeaders)) {
|
||||||
console.error('[TiledImage.setAjaxHeaders] Ignoring invalid headers, must be a plain object');
|
$.console.error('[TiledImage.setAjaxHeaders] Ignoring invalid headers, must be a plain object');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1881,32 +1881,13 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
* @param {OpenSeadragon.Tile} tile
|
* @param {OpenSeadragon.Tile} tile
|
||||||
*/
|
*/
|
||||||
_tryFindTileCacheRecord: function(tile) {
|
_tryFindTileCacheRecord: function(tile) {
|
||||||
let record = this._tileCache.getCacheRecord(tile.cacheKey);
|
let record = this._tileCache.getCacheRecord(tile.originalCacheKey);
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
tile.loading = true;
|
||||||
// if we find existing record, check the original data of existing tile of this record
|
this._setTileLoaded(tile, record.data, null, null, record.type);
|
||||||
let baseTile = record._tiles[0];
|
|
||||||
if (!baseTile) {
|
|
||||||
// zombie cache -> revive, it's okay to use current tile as state inherit point since there is no state
|
|
||||||
baseTile = tile;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup tile manually, data can be null -> we already have existing cache to share, share also caches
|
|
||||||
tile.tiledImage = this;
|
|
||||||
tile.addCache(baseTile.originalCacheKey, null, record.type, false, false);
|
|
||||||
if (baseTile.cacheKey !== baseTile.originalCacheKey) {
|
|
||||||
tile.addCache(baseTile.cacheKey, null, record.type, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
tile.hasTransparency = tile.hasTransparency || this.source.hasTransparency(
|
|
||||||
undefined, tile.getUrl(), tile.ajaxHeaders, tile.postData
|
|
||||||
);
|
|
||||||
|
|
||||||
tile.loading = false;
|
|
||||||
tile.loaded = true;
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -2154,7 +2135,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
function markTileAsReady() {
|
function markTileAsReady() {
|
||||||
tile.lastProcess = false;
|
tile.lastProcess = false;
|
||||||
tile.processing = false;
|
tile.processing = false;
|
||||||
tile.transforming = false;
|
|
||||||
|
|
||||||
const fallbackCompletion = getCompletionCallback();
|
const fallbackCompletion = getCompletionCallback();
|
||||||
|
|
||||||
@ -2185,16 +2165,16 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
resolver = resolve;
|
resolver = resolve;
|
||||||
}),
|
}),
|
||||||
get image() {
|
get image() {
|
||||||
$.console.error("[tile-loaded] event 'image' has been deprecated. Use 'tile.getData()' instead.");
|
$.console.error("[tile-loaded] event 'image' has been deprecated. Use 'tile-invalidated' event to modify data instead.");
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
get data() {
|
get data() {
|
||||||
$.console.error("[tile-loaded] event 'data' has been deprecated. Use 'tile.getData()' instead.");
|
$.console.error("[tile-loaded] event 'data' has been deprecated. Use 'tile-invalidated' event to modify data instead.");
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
getCompletionCallback: function () {
|
getCompletionCallback: function () {
|
||||||
$.console.error("[tile-loaded] getCompletionCallback is deprecated: it introduces race conditions: " +
|
$.console.error("[tile-loaded] getCompletionCallback is deprecated: it introduces race conditions: " +
|
||||||
"use async event handlers instead, execution order is deducted by addHandler(...) priority");
|
"use async event handlers instead, execution order is deducted by addHandler(...) priority argument.");
|
||||||
return getCompletionCallback();
|
return getCompletionCallback();
|
||||||
},
|
},
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
@ -2207,8 +2187,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
const updatePromise = _this.viewer.world.requestTileInvalidateEvent([tile], now, false, true);
|
const updatePromise = _this.viewer.world.requestTileInvalidateEvent([tile], now, false, true);
|
||||||
updatePromise.then(markTileAsReady);
|
updatePromise.then(markTileAsReady);
|
||||||
} else {
|
} else {
|
||||||
// In case we did not succeed in tile restoration, request invalidation
|
// Tile-invalidated not called on each tile, but only on tiles with new data! Verify we share the main cache
|
||||||
// Tile-loaded not called on each tile, but only on tiles with new data! Verify we share the main cache
|
|
||||||
const origCache = tile.getCache(tile.originalCacheKey);
|
const origCache = tile.getCache(tile.originalCacheKey);
|
||||||
for (let t of origCache._tiles) {
|
for (let t of origCache._tiles) {
|
||||||
|
|
||||||
@ -2218,11 +2197,18 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
|||||||
// add reference also to the main cache, no matter what the other tile state has
|
// add reference also to the main cache, no matter what the other tile state has
|
||||||
// completion of the invaldate event should take care of all such tiles
|
// completion of the invaldate event should take care of all such tiles
|
||||||
const targetMainCache = t.getCache();
|
const targetMainCache = t.getCache();
|
||||||
tile.addCache(t.cacheKey, () => {
|
tile.setCache(t.cacheKey, targetMainCache, true, false);
|
||||||
$.console.error("Attempt to share main cache with existing tile should not trigger data getter!");
|
|
||||||
return targetMainCache.data;
|
|
||||||
}, targetMainCache.type, true, false);
|
|
||||||
break;
|
break;
|
||||||
|
} else if (t.processing) {
|
||||||
|
console.log("ENCOUNTERED LOADING TILE!!!");
|
||||||
|
let internval = setInterval(() => {
|
||||||
|
if (t.processing) {
|
||||||
|
clearInterval(internval);
|
||||||
|
console.log("FINISHED!!!!!");
|
||||||
|
markTileAsReady();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
markTileAsReady();
|
markTileAsReady();
|
||||||
|
@ -1135,7 +1135,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
|
|||||||
ajaxHeaders = {};
|
ajaxHeaders = {};
|
||||||
}
|
}
|
||||||
if (!$.isPlainObject(ajaxHeaders)) {
|
if (!$.isPlainObject(ajaxHeaders)) {
|
||||||
console.error('[Viewer.setAjaxHeaders] Ignoring invalid headers, must be a plain object');
|
$.console.error('[Viewer.setAjaxHeaders] Ignoring invalid headers, must be a plain object');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (propagate === undefined) {
|
if (propagate === undefined) {
|
||||||
|
97
src/world.js
97
src/world.js
@ -276,7 +276,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
|
|||||||
// We allow re-execution on tiles that are in process but have too low processing timestamp,
|
// We allow re-execution on tiles that are in process but have too low processing timestamp,
|
||||||
// which must be solved by ensuring subsequent data calls in the suddenly outdated processing
|
// which must be solved by ensuring subsequent data calls in the suddenly outdated processing
|
||||||
// pipeline take no effect.
|
// pipeline take no effect.
|
||||||
if (!tile || (!_allowTileUnloaded && !tile.loaded) || tile.transforming) {
|
if (!tile || (!_allowTileUnloaded && !tile.loaded)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const tileCache = tile.getCache(tile.originalCacheKey);
|
const tileCache = tile.getCache(tile.originalCacheKey);
|
||||||
@ -308,20 +308,96 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
|
|||||||
const drawerId = this.viewer.drawer.getId();
|
const drawerId = this.viewer.drawer.getId();
|
||||||
|
|
||||||
const jobList = tileList.map(tile => {
|
const jobList = tileList.map(tile => {
|
||||||
if (restoreTiles) {
|
const tiledImage = tile.tiledImage;
|
||||||
tile.restore();
|
const originalCache = tile.getCache(tile.originalCacheKey);
|
||||||
|
let workingCache = null;
|
||||||
|
const getWorkingCacheData = (type) => {
|
||||||
|
if (workingCache) {
|
||||||
|
return workingCache.getDataAs(type, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const targetCopyKey = restoreTiles ? tile.originalCacheKey : tile.cacheKey;
|
||||||
|
const origCache = tile.getCache(targetCopyKey);
|
||||||
|
if (!origCache) {
|
||||||
|
$.console.error("[Tile::getData] There is no cache available for tile with key %s", targetCopyKey);
|
||||||
|
return $.Promise.reject();
|
||||||
|
}
|
||||||
|
// Here ensure type is defined, rquired by data callbacks
|
||||||
|
type = type || origCache.type;
|
||||||
|
workingCache = new $.CacheRecord().withTileReference(tile);
|
||||||
|
return origCache.getDataAs(type, true).then(data => {
|
||||||
|
workingCache.addTile(tile, data, type);
|
||||||
|
return workingCache.data;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const setWorkingCacheData = (value, type) => {
|
||||||
|
if (!workingCache) {
|
||||||
|
workingCache = new $.CacheRecord().withTileReference(tile);
|
||||||
|
workingCache.addTile(tile, value, type);
|
||||||
|
} else {
|
||||||
|
workingCache.setDataAs(value, type);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const atomicCacheSwap = () => {
|
||||||
|
if (workingCache) {
|
||||||
|
let newCacheKey = tile.buildDistinctMainCacheKey();
|
||||||
|
tiledImage._tileCache.injectCache({
|
||||||
|
tile: tile,
|
||||||
|
cache: workingCache,
|
||||||
|
targetKey: newCacheKey,
|
||||||
|
setAsMainCache: true,
|
||||||
|
tileAllowNotLoaded: false //todo what if called from load event?
|
||||||
|
});
|
||||||
|
} else if (restoreTiles) {
|
||||||
|
// If we requested restore, perform now
|
||||||
|
tiledImage._tileCache.restoreTilesThatShareOriginalCache(tile, tile.getCache(tile.originalCacheKey), true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//todo docs
|
||||||
return eventTarget.raiseEventAwaiting('tile-invalidated', {
|
return eventTarget.raiseEventAwaiting('tile-invalidated', {
|
||||||
tile: tile,
|
tile: tile,
|
||||||
tiledImage: tile.tiledImage,
|
tiledImage: tiledImage,
|
||||||
}, tile.getCache(tile.originalCacheKey)).then(cacheKey => {
|
outdated: () => originalCache.__invStamp !== tStamp,
|
||||||
if (cacheKey.__invStamp === tStamp) {
|
getData: getWorkingCacheData,
|
||||||
// asynchronous finisher
|
setData: setWorkingCacheData,
|
||||||
tile.transforming = tStamp;
|
resetData: () => {
|
||||||
return tile.updateRenderTargetWithDataTransform(drawerId, supportedFormats, keepInternalCacheCopy, tStamp).then(() => {
|
workingCache.destroy();
|
||||||
cacheKey.__invStamp = null;
|
workingCache = null;
|
||||||
|
}
|
||||||
|
}).then(_ => {
|
||||||
|
if (originalCache.__invStamp === tStamp) {
|
||||||
|
if (workingCache) {
|
||||||
|
return workingCache.prepareForRendering(drawerId, supportedFormats, keepInternalCacheCopy).then(c => {
|
||||||
|
if (c && originalCache.__invStamp === tStamp) {
|
||||||
|
atomicCacheSwap();
|
||||||
|
originalCache.__invStamp = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we requested restore, perform now
|
||||||
|
if (restoreTiles) {
|
||||||
|
const freshOriginalCacheRef = tile.getCache(tile.originalCacheKey);
|
||||||
|
|
||||||
|
tiledImage._tileCache.restoreTilesThatShareOriginalCache(tile, freshOriginalCacheRef, true);
|
||||||
|
return freshOriginalCacheRef.prepareForRendering(drawerId, supportedFormats, keepInternalCacheCopy).then((c) => {
|
||||||
|
if (c && originalCache.__invStamp === tStamp) {
|
||||||
|
atomicCacheSwap();
|
||||||
|
originalCache.__invStamp = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const freshMainCacheRef = tile.getCache();
|
||||||
|
return freshMainCacheRef.prepareForRendering(drawerId, supportedFormats, keepInternalCacheCopy).then(() => {
|
||||||
|
originalCache.__invStamp = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (workingCache) {
|
||||||
|
workingCache.destroy();
|
||||||
|
workingCache = null;
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
$.console.error("Update routine error:", e);
|
$.console.error("Update routine error:", e);
|
||||||
@ -332,7 +408,6 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
|
|||||||
for (let tile of markedTiles) {
|
for (let tile of markedTiles) {
|
||||||
tile.lastProcess = false;
|
tile.lastProcess = false;
|
||||||
tile.processing = false;
|
tile.processing = false;
|
||||||
tile.transforming = false;
|
|
||||||
}
|
}
|
||||||
this.draw();
|
this.draw();
|
||||||
});
|
});
|
||||||
|
@ -807,7 +807,6 @@ async function processTile(tile) {
|
|||||||
console.log("Selected tile", tile);
|
console.log("Selected tile", tile);
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
updateCanvas(document.getElementById("tile-original"), tile, tile.originalCacheKey),
|
updateCanvas(document.getElementById("tile-original"), tile, tile.originalCacheKey),
|
||||||
updateCanvas(document.getElementById("tile-working"), tile, tile._wcKey),
|
|
||||||
updateCanvas(document.getElementById("tile-main"), tile, tile.cacheKey),
|
updateCanvas(document.getElementById("tile-main"), tile, tile.cacheKey),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,6 @@
|
|||||||
|
|
||||||
<div style="display: flex">
|
<div style="display: flex">
|
||||||
<div id="tile-original"></div>
|
<div id="tile-original"></div>
|
||||||
<div id="tile-working"></div>
|
|
||||||
<div id="tile-main"></div>
|
<div id="tile-main"></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -56,15 +56,15 @@
|
|||||||
setOptions(this, options);
|
setOptions(this, options);
|
||||||
|
|
||||||
async function applyFilters(e) {
|
async function applyFilters(e) {
|
||||||
const tile = e.tile,
|
const tiledImage = e.tiledImage,
|
||||||
tiledImage = e.tiledImage,
|
|
||||||
processors = getFiltersProcessors(self, tiledImage);
|
processors = getFiltersProcessors(self, tiledImage);
|
||||||
|
|
||||||
if (processors.length === 0) {
|
if (processors.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextCopy = await tile.getData('context2d');
|
const contextCopy = await e.getData('context2d');
|
||||||
|
if (!contextCopy) return;
|
||||||
|
|
||||||
if (contextCopy.canvas.width === 0) {
|
if (contextCopy.canvas.width === 0) {
|
||||||
debugger;
|
debugger;
|
||||||
@ -79,7 +79,7 @@
|
|||||||
await processors[i](contextCopy);
|
await processors[i](contextCopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
await tile.setData(contextCopy, 'context2d');
|
await e.setData(contextCopy, 'context2d');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// pass, this is error caused by canvas being destroyed & replaced
|
// pass, this is error caused by canvas being destroyed & replaced
|
||||||
}
|
}
|
||||||
|
@ -89,9 +89,9 @@
|
|||||||
<textarea id="scriptInput" rows="25" cols="120" placeholder="" style="height: 470px">
|
<textarea id="scriptInput" rows="25" cols="120" placeholder="" style="height: 470px">
|
||||||
// window.pluginA must be defined! draw small gradient square
|
// window.pluginA must be defined! draw small gradient square
|
||||||
window.pluginA = async function(e) {
|
window.pluginA = async function(e) {
|
||||||
const tile = e.tile;
|
const ctx = await e.getData('context2d');
|
||||||
const ctx = await tile.getData('context2d');
|
|
||||||
|
|
||||||
|
if (ctx) {
|
||||||
const gradient = ctx.createLinearGradient(0, 0, 50, 50);
|
const gradient = ctx.createLinearGradient(0, 0, 50, 50);
|
||||||
gradient.addColorStop(0, 'blue');
|
gradient.addColorStop(0, 'blue');
|
||||||
gradient.addColorStop(0.5, 'green');
|
gradient.addColorStop(0.5, 'green');
|
||||||
@ -99,15 +99,18 @@ window.pluginA = async function(e) {
|
|||||||
ctx.fillStyle = gradient;
|
ctx.fillStyle = gradient;
|
||||||
ctx.fillRect(0, 0, 50, 50);
|
ctx.fillRect(0, 0, 50, 50);
|
||||||
|
|
||||||
await tile.setData(ctx, 'context2d');
|
await e.setData(ctx, 'context2d');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// window.pluginB must be defined! overlay with color opacity 40%
|
// window.pluginB must be defined! overlay with color opacity 40%
|
||||||
window.pluginB = async function(e) {
|
window.pluginB = async function(e) {
|
||||||
const tile = e.tile;
|
const ctx = await e.getData('context2d');
|
||||||
const ctx = await tile.getData('context2d'), canvas = ctx.canvas;
|
if (ctx) {
|
||||||
|
const canvas = ctx.canvas;
|
||||||
ctx.fillStyle = "rgba(156, 0, 26, 0.4)";
|
ctx.fillStyle = "rgba(156, 0, 26, 0.4)";
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
await tile.setData(ctx, 'context2d');
|
await tile.setData(ctx, 'context2d');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// higher number = earlier execution
|
// higher number = earlier execution
|
||||||
window.orderPluginA = 1;
|
window.orderPluginA = 1;
|
||||||
|
@ -25,18 +25,19 @@
|
|||||||
|
|
||||||
function getPluginCode(overlayColor = "rgba(0,0,255,0.5)") {
|
function getPluginCode(overlayColor = "rgba(0,0,255,0.5)") {
|
||||||
return async function(e) {
|
return async function(e) {
|
||||||
const tile = e.tile;
|
const ctx = await e.getData('context2d');
|
||||||
const ctx = await tile.getData('context2d'), canvas = ctx.canvas;
|
if (ctx) {
|
||||||
|
const canvas = ctx.canvas;
|
||||||
ctx.fillStyle = overlayColor;
|
ctx.fillStyle = overlayColor;
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
await tile.setData(ctx, 'context2d');
|
await e.setData(ctx, 'context2d');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getResetTileDataCode() {
|
function getResetTileDataCode() {
|
||||||
return async function(e) {
|
return async function(e) {
|
||||||
const tile = e.tile;
|
e.resetData();
|
||||||
tile.restore();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user