mirror of
https://github.com/openseadragon/openseadragon.git
synced 2025-03-14 04:56:19 +03:00
First visually correct design: tile invalidation event manages three caches that are shared among equal tiles (based on cache key). Works with both latest drawers and shared caches.
This commit is contained in:
parent
cba40f4db8
commit
29b01cf1bd
@ -46,6 +46,8 @@
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt test",
|
||||
"prepare": "grunt build"
|
||||
"prepare": "grunt build",
|
||||
"build": "grunt build",
|
||||
"dev": "grunt connect watch"
|
||||
}
|
||||
}
|
||||
|
@ -196,6 +196,9 @@ $.DataTypeConvertor = class {
|
||||
|
||||
// Teaching OpenSeadragon built-in conversions:
|
||||
const imageCreator = (tile, url) => new $.Promise((resolve, reject) => {
|
||||
if (!$.supportsAsync) {
|
||||
throw "Not supported in sync mode!";
|
||||
}
|
||||
const img = new Image();
|
||||
img.onerror = img.onabort = reject;
|
||||
img.onload = () => resolve(img);
|
||||
@ -342,7 +345,7 @@ $.DataTypeConvertor = class {
|
||||
convert(tile, data, from, ...to) {
|
||||
const conversionPath = this.getConversionPath(from, to);
|
||||
if (!conversionPath) {
|
||||
$.console.error(`[OpenSeadragon.convertor.convert] Conversion conversion ${from} ---> ${to} cannot be done!`);
|
||||
$.console.error(`[OpenSeadragon.convertor.convert] Conversion ${from} ---> ${to} cannot be done!`);
|
||||
return $.Promise.resolve();
|
||||
}
|
||||
|
||||
|
@ -58,6 +58,7 @@ OpenSeadragon.DrawerBase = class DrawerBase{
|
||||
$.console.assert( options.viewport, "[Drawer] options.viewport is required" );
|
||||
$.console.assert( options.element, "[Drawer] options.element is required" );
|
||||
|
||||
this._id = this.getType() + $.now();
|
||||
this.viewer = options.viewer;
|
||||
this.viewport = options.viewport;
|
||||
this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;
|
||||
@ -110,6 +111,14 @@ OpenSeadragon.DrawerBase = class DrawerBase{
|
||||
return this.container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unique drawer ID
|
||||
* @return {string}
|
||||
*/
|
||||
getId() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @abstract
|
||||
* @returns {String | undefined} What type of drawer this is. Must be overridden by extending classes.
|
||||
@ -142,7 +151,7 @@ OpenSeadragon.DrawerBase = class DrawerBase{
|
||||
$.console.warn("Attempt to draw tile %s when not cached!", tile);
|
||||
return null;
|
||||
}
|
||||
return cache.getDataForRendering(this);
|
||||
return cache.getDataForRendering(this, tile);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1080,6 +1080,14 @@ function OpenSeadragon( options ){
|
||||
return supported >= 3;
|
||||
}());
|
||||
|
||||
/**
|
||||
* If true, OpenSeadragon uses async execution, else it uses synchronous execution.
|
||||
* Note that disabling async means no plugins that use Promises / async will work with OSD.
|
||||
* @member {boolean}
|
||||
* @memberof OpenSeadragon
|
||||
*/
|
||||
$.supportsAsync = true;
|
||||
|
||||
/**
|
||||
* A ratio comparing the device screen's pixel density to the canvas's backing store pixel density,
|
||||
* clamped to a minimum of 1. Defaults to 1 if canvas isn't supported by the browser.
|
||||
@ -2622,53 +2630,6 @@ function OpenSeadragon( options ){
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
$.extend(FILEFORMATS, formats);
|
||||
},
|
||||
|
||||
|
||||
//@private, runs non-invasive update of all tiles given in the list
|
||||
invalidateTilesLater: function(tileList, tStamp, viewer) {
|
||||
if (tileList.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
function finish () {
|
||||
const tile = tileList[0];
|
||||
const tiledImage = tile.tiledImage;
|
||||
tiledImage.invalidatedFinishAt = tiledImage.invalidatedAt;
|
||||
for (let tile of tileList) {
|
||||
tile.render();
|
||||
}
|
||||
viewer.forceRedraw();
|
||||
}
|
||||
|
||||
$.Promise.all(tileList.map(tile => {
|
||||
if (!tile.loaded) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tiledImage = tile.tiledImage;
|
||||
if (tiledImage.invalidatedAt > tStamp) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tileCache = tile.getCache();
|
||||
if (tileCache._updateStamp >= tStamp) {
|
||||
return undefined;
|
||||
}
|
||||
tileCache._updateStamp = tStamp;
|
||||
return viewer.raiseEventAwaiting('tile-needs-update', {
|
||||
tile: tile,
|
||||
tiledImage: tile.tiledImage,
|
||||
}).then(() => {
|
||||
// TODO: check that the user has finished tile update and if not, rename cache key or throw
|
||||
const newCache = tile.getCache();
|
||||
if (newCache) {
|
||||
newCache._updateStamp = tStamp;
|
||||
} else {
|
||||
$.console.error("After an update, the tile %s has not cache data! Check handlers on 'tile-needs-update' event!", tile);
|
||||
}
|
||||
});
|
||||
})).catch(finish).then(finish);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -2935,90 +2896,97 @@ function OpenSeadragon( options ){
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise proxy in OpenSeadragon, can be removed once IE11 support is dropped
|
||||
* Promise proxy in OpenSeadragon, enables $.supportsAsync feature.
|
||||
* @type {PromiseConstructor}
|
||||
*/
|
||||
$.Promise = (function () {
|
||||
return class {
|
||||
constructor(handler) {
|
||||
this._error = false;
|
||||
this.__value = undefined;
|
||||
$.Promise = window["Promise"] && $.supportsAsync ? window["Promise"] : class {
|
||||
constructor(handler) {
|
||||
this._error = false;
|
||||
this.__value = undefined;
|
||||
|
||||
try {
|
||||
handler(
|
||||
(value) => {
|
||||
this._value = value;
|
||||
},
|
||||
(error) => {
|
||||
this._value = error;
|
||||
this._error = true;
|
||||
try {
|
||||
// Make sure to unwrap all nested promises!
|
||||
handler(
|
||||
(value) => {
|
||||
while (value instanceof $.Promise) {
|
||||
value = value._value;
|
||||
}
|
||||
);
|
||||
this._value = value;
|
||||
},
|
||||
(error) => {
|
||||
while (error instanceof $.Promise) {
|
||||
error = error._value;
|
||||
}
|
||||
this._value = error;
|
||||
this._error = true;
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
this._value = e;
|
||||
this._error = true;
|
||||
}
|
||||
}
|
||||
|
||||
then(handler) {
|
||||
if (!this._error) {
|
||||
try {
|
||||
this._value = handler(this._value);
|
||||
} catch (e) {
|
||||
this._value = e;
|
||||
this._error = true;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
then(handler) {
|
||||
if (!this._error) {
|
||||
try {
|
||||
this._value = handler(this._value);
|
||||
} catch (e) {
|
||||
this._value = e;
|
||||
this._error = true;
|
||||
}
|
||||
catch(handler) {
|
||||
if (this._error) {
|
||||
try {
|
||||
this._value = handler(this._value);
|
||||
this._error = false;
|
||||
} catch (e) {
|
||||
this._value = e;
|
||||
this._error = true;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
catch(handler) {
|
||||
if (this._error) {
|
||||
try {
|
||||
this._value = handler(this._value);
|
||||
this._error = false;
|
||||
} catch (e) {
|
||||
this._value = e;
|
||||
this._error = true;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
get _value() {
|
||||
return this.__value;
|
||||
}
|
||||
set _value(val) {
|
||||
if (val && val.constructor === this.constructor) {
|
||||
val = val._value; //unwrap
|
||||
}
|
||||
this.__value = val;
|
||||
}
|
||||
|
||||
get _value() {
|
||||
return this.__value;
|
||||
}
|
||||
set _value(val) {
|
||||
if (val && val.constructor === this.constructor) {
|
||||
val = val._value; //unwrap
|
||||
}
|
||||
this.__value = val;
|
||||
}
|
||||
static resolve(value) {
|
||||
return new this((resolve) => resolve(value));
|
||||
}
|
||||
|
||||
static resolve(value) {
|
||||
return new this((resolve) => resolve(value));
|
||||
}
|
||||
static reject(error) {
|
||||
return new this((_, reject) => reject(error));
|
||||
}
|
||||
|
||||
static reject(error) {
|
||||
return new this((_, reject) => reject(error));
|
||||
}
|
||||
static all(functions) {
|
||||
return new this((resolve) => {
|
||||
// no async support, just execute them
|
||||
return resolve(functions.map(fn => fn()));
|
||||
});
|
||||
}
|
||||
|
||||
static all(functions) {
|
||||
return functions.map(fn => new this(fn));
|
||||
static race(functions) {
|
||||
if (functions.length < 1) {
|
||||
return this.resolve();
|
||||
}
|
||||
|
||||
static race(functions) {
|
||||
if (functions.length < 1) {
|
||||
return undefined;
|
||||
}
|
||||
return new this(functions[0]);
|
||||
}
|
||||
};
|
||||
// if (window.Promise) {
|
||||
// return window.Promise;
|
||||
// }
|
||||
// todo let users chose sync/async
|
||||
})();
|
||||
// no async support, just execute the first
|
||||
return new this((resolve) => {
|
||||
return resolve(functions[0]());
|
||||
});
|
||||
}
|
||||
};
|
||||
}(OpenSeadragon));
|
||||
|
||||
|
||||
|
182
src/tile.js
182
src/tile.js
@ -33,6 +33,7 @@
|
||||
*/
|
||||
|
||||
(function( $ ){
|
||||
let _workingCacheIdDealer = 0;
|
||||
|
||||
/**
|
||||
* @class Tile
|
||||
@ -267,14 +268,26 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
|
||||
this.tiledImage = null;
|
||||
/**
|
||||
* Array of cached tile data associated with the tile.
|
||||
* @member {Object} _caches
|
||||
* @member {Object}
|
||||
* @private
|
||||
*/
|
||||
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._cacheSize = 0;
|
||||
this._wcKey = `w${_workingCacheIdDealer++}://` + this.originalCacheKey;
|
||||
/**
|
||||
* Processing flag, exempt the tile from removal when there are ongoing updates
|
||||
* @member {Boolean}
|
||||
* @private
|
||||
*/
|
||||
this.processing = false;
|
||||
};
|
||||
|
||||
/** @lends OpenSeadragon.Tile.prototype */
|
||||
@ -449,72 +462,137 @@ $.Tile.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the data to render for this tile
|
||||
* Get the data to render for this tile. If no conversion is necessary, get a reference. Else, get a copy
|
||||
* of the data as desired type. This means that data modification _might_ be reflected on the tile, but
|
||||
* it is not guaranteed. Use tile.setData() to ensure changes are reflected.
|
||||
* @param {string} type data type to require
|
||||
* @param {boolean} [copy=true] whether to force copy retrieval
|
||||
* @return {*|undefined} data in the desired type, or undefined if a conversion is ongoing
|
||||
* @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, copy = true) {
|
||||
getData: function(type) {
|
||||
if (!this.tiledImage) {
|
||||
return null; //async can access outside its lifetime
|
||||
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.cacheKey);
|
||||
const cache = this.getCache(this._wcKey);
|
||||
if (!cache) {
|
||||
$.console.error("[Tile::getData] There is no cache available for tile with key " + this.cacheKey);
|
||||
return undefined;
|
||||
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, copy);
|
||||
return cache.getDataAs(type, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the original data data for this tile
|
||||
* @param {string} type data type to require
|
||||
* @param {boolean} [copy=this.loaded] whether to force copy retrieval
|
||||
* note that if you do not copy the data and save the data to a different cache,
|
||||
* its destruction will also delete this original data which will likely cause issues
|
||||
* @return {*|undefined} data in the desired type, or undefined if a conversion is ongoing
|
||||
* Restore the original data data for this tile
|
||||
* @param {boolean} freeIfUnused if true, restoration frees cache along the way of the tile lifecycle
|
||||
*/
|
||||
getOriginalData: function(type, copy = true) {
|
||||
restore: function(freeIfUnused = true) {
|
||||
if (!this.tiledImage) {
|
||||
return null; //async can access outside its lifetime
|
||||
return; //async context can access the tile outside its lifetime
|
||||
}
|
||||
|
||||
//we return the data synchronously immediatelly (undefined if conversion happens)
|
||||
const cache = this.getCache(this.originalCacheKey);
|
||||
if (!cache) {
|
||||
$.console.error("[Tile::getData] There is no cache available for tile with key " + this.originalCacheKey);
|
||||
return undefined;
|
||||
if (this.originalCacheKey !== this.cacheKey) {
|
||||
this.__restoreRequestedFree = freeIfUnused;
|
||||
this.__restore = true;
|
||||
}
|
||||
return cache.getDataAs(type, copy);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set main cache data
|
||||
* @param {*} value
|
||||
* @param {?string} type data type to require
|
||||
* @param {boolean} [preserveOriginalData=true] if true and cacheKey === originalCacheKey,
|
||||
* @return {OpenSeadragon.Promise<*>}
|
||||
*/
|
||||
setData: function(value, type, preserveOriginalData = true) {
|
||||
setData: function(value, type) {
|
||||
if (!this.tiledImage) {
|
||||
return null; //async can access outside its lifetime
|
||||
return null; //async context can access the tile outside its lifetime
|
||||
}
|
||||
|
||||
if (preserveOriginalData && this.cacheKey === this.originalCacheKey) {
|
||||
//caches equality means we have only one cache:
|
||||
// create new cache record with main cache key changed to 'mod'
|
||||
return this.addCache("mod://" + this.originalCacheKey, value, type, true)._promise;
|
||||
}
|
||||
//else overwrite cache
|
||||
const cache = this.getCache(this.cacheKey);
|
||||
const cache = this.getCache(this._wcKey);
|
||||
if (!cache) {
|
||||
$.console.error("[Tile::setData] There is no cache available for tile with key " + this.cacheKey);
|
||||
$.console.error("[Tile::setData] You cannot set data without calling tile.getData()! The working cache is not initialized!");
|
||||
return $.Promise.resolve();
|
||||
}
|
||||
return cache.setDataAs(value, type);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Optimizazion: prepare target cache for subsequent use in rendering, and perform updateRenderTarget()
|
||||
* @private
|
||||
*/
|
||||
updateRenderTargetWithDataTransform: function (drawerId, supportedFormats, usePrivateCache) {
|
||||
// Now, if working cache exists, we set main cache to the working cache --> prepare
|
||||
const cache = this.getCache(this._wcKey);
|
||||
if (cache) {
|
||||
return cache.prepareForRendering(drawerId, supportedFormats, usePrivateCache, this.processing);
|
||||
}
|
||||
|
||||
// If we requested restore, perform now
|
||||
if (this.__restore) {
|
||||
const cache = this.getCache(this.originalCacheKey);
|
||||
|
||||
this.tiledImage._tileCache.restoreTilesThatShareOriginalCache(
|
||||
this, cache
|
||||
);
|
||||
this.__restore = false;
|
||||
return cache.prepareForRendering(drawerId, supportedFormats, usePrivateCache, this.processing);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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)
|
||||
* @private
|
||||
* @return
|
||||
*/
|
||||
updateRenderTarget: function () {
|
||||
// TODO we probably need to create timestamp and check if current update stamp is the one saved on the cache,
|
||||
// if yes, then the update has been performed (and update all tiles asociated to the same cache at once)
|
||||
// since we cannot ensure all tiles are called with the update (e.g. zombies)
|
||||
// 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;
|
||||
|
||||
//TODO IMPLEMENT LOCKING AND IGNORE PIPELINE OUT OF THESE CALLS
|
||||
|
||||
// Now, if working cache exists, we set main cache to the working cache, since it has been updated
|
||||
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
|
||||
});
|
||||
this.cacheKey = newCacheKey;
|
||||
return;
|
||||
}
|
||||
// If we requested restore, perform now
|
||||
if (requestedRestore) {
|
||||
this.tiledImage._tileCache.restoreTilesThatShareOriginalCache(
|
||||
this, this.getCache(this.originalCacheKey)
|
||||
);
|
||||
}
|
||||
// Else no work to be done
|
||||
},
|
||||
|
||||
/**
|
||||
* Read tile cache data object (CacheRecord)
|
||||
* @param {string} [key=this.cacheKey] cache key to read that belongs to this tile
|
||||
@ -572,9 +650,6 @@ $.Tile.prototype = {
|
||||
});
|
||||
const havingRecord = this._caches[key];
|
||||
if (havingRecord !== cachedItem) {
|
||||
if (!havingRecord) {
|
||||
this._cacheSize++;
|
||||
}
|
||||
this._caches[key] = cachedItem;
|
||||
}
|
||||
|
||||
@ -607,7 +682,7 @@ $.Tile.prototype = {
|
||||
* @returns {number} number of caches
|
||||
*/
|
||||
getCacheSize: function() {
|
||||
return this._cacheSize;
|
||||
return Object.values(this._caches).length;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -646,7 +721,6 @@ $.Tile.prototype = {
|
||||
}
|
||||
if (this.tiledImage._tileCache.unloadCacheForTile(this, key, freeIfUnused)) {
|
||||
//if we managed to free tile from record, we are sure we decreased cache count
|
||||
this._cacheSize--;
|
||||
delete this._caches[key];
|
||||
}
|
||||
},
|
||||
@ -694,6 +768,30 @@ $.Tile.prototype = {
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reflect that a cache object was renamed. Called internally from TileCache.
|
||||
* Do NOT call manually.
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
reflectCacheRenamed: function (oldKey, newKey) {
|
||||
let cache = this._caches[oldKey];
|
||||
if (!cache) {
|
||||
return; // nothing to fix
|
||||
}
|
||||
// Do update via private refs, old key no longer exists in cache
|
||||
if (oldKey === this._ocKey) {
|
||||
this._ocKey = newKey;
|
||||
}
|
||||
if (oldKey === this._cKey) {
|
||||
this._cKey = newKey;
|
||||
}
|
||||
// Working key is never updated, it will be invalidated (but do not dereference cache, just fix the pointers)
|
||||
this._caches[newKey] = cache;
|
||||
cache.AAA = true;
|
||||
delete this._caches[oldKey];
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes tile from its container.
|
||||
* @function
|
||||
@ -707,7 +805,7 @@ $.Tile.prototype = {
|
||||
this.element.parentNode.removeChild( this.element );
|
||||
}
|
||||
this.tiledImage = null;
|
||||
this._caches = [];
|
||||
this._caches = {};
|
||||
this._cacheSize = 0;
|
||||
this.element = null;
|
||||
this.imgElement = null;
|
||||
|
344
src/tilecache.js
344
src/tilecache.js
@ -121,20 +121,20 @@
|
||||
|
||||
/**
|
||||
* Access the cache record data indirectly. Preferred way of data access. Asynchronous.
|
||||
* @param {string} [type=this.type]
|
||||
* @param {string} [type=undefined]
|
||||
* @param {boolean} [copy=true] if false and same type is retrieved as the cache type,
|
||||
* copy is not performed: note that this is potentially dangerous as it might
|
||||
* introduce race conditions (you get a cache data direct reference you modify).
|
||||
* @returns {OpenSeadragon.Promise<?>} desired data type in promise, undefined if the cache was destroyed
|
||||
*/
|
||||
getDataAs(type = this._type, copy = true) {
|
||||
getDataAs(type = undefined, copy = true) {
|
||||
if (this.loaded) {
|
||||
if (type === this._type) {
|
||||
return copy ? $.convertor.copy(this._tRef, this._data, type) : this._promise;
|
||||
return copy ? $.convertor.copy(this._tRef, this._data, type || this._type) : this._promise;
|
||||
}
|
||||
return this._transformDataIfNeeded(this._tRef, this._data, type, copy) || this._promise;
|
||||
return this._transformDataIfNeeded(this._tRef, this._data, type || this._type, copy) || this._promise;
|
||||
}
|
||||
return this._promise.then(data => this._transformDataIfNeeded(this._tRef, data, type, copy) || data);
|
||||
return this._promise.then(data => this._transformDataIfNeeded(this._tRef, data, type || this._type, copy) || data);
|
||||
}
|
||||
|
||||
_transformDataIfNeeded(referenceTile, data, type, copy) {
|
||||
@ -178,9 +178,18 @@
|
||||
return this.data;
|
||||
}
|
||||
|
||||
if (this._destroyed) {
|
||||
$.console.error("Attempt to draw tile with destroyed main cache!");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let internalCache = this[DRAWER_INTERNAL_CACHE];
|
||||
internalCache = internalCache && internalCache[drawer.getId()];
|
||||
if (keepInternalCopy && !internalCache) {
|
||||
this.prepareForRendering(supportedTypes, keepInternalCopy)
|
||||
$.console.warn("Attempt to render tile that is not prepared with drawer requesting " +
|
||||
"internal cache! This might introduce artifacts.");
|
||||
|
||||
this.prepareForRendering(drawer.getId(), supportedTypes, keepInternalCopy)
|
||||
.then(() => this._triggerNeedsDraw());
|
||||
return undefined;
|
||||
}
|
||||
@ -198,24 +207,40 @@
|
||||
}
|
||||
|
||||
if (!supportedTypes.includes(internalCache.type)) {
|
||||
$.console.warn("Attempt to render tile that is not prepared for current drawer supported format: " +
|
||||
"the preparation should've happened after tile processing has finished.");
|
||||
|
||||
internalCache.transformTo(supportedTypes.length > 1 ? supportedTypes : supportedTypes[0])
|
||||
.then(() => this._triggerNeedsDraw());
|
||||
return undefined; // type is NOT compatible
|
||||
}
|
||||
|
||||
return internalCache.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should not be called if cache type is already among supported types
|
||||
* @private
|
||||
* @param drawerId
|
||||
* @param supportedTypes
|
||||
* @param keepInternalCopy
|
||||
* @param _shareTileUpdateStamp private param, updates render target (swap cache memory) for tiles that come
|
||||
* from the same tstamp batch
|
||||
* @return {OpenSeadragon.Promise<OpenSeadragon.SimpleCacheRecord|OpenSeadragon.CacheRecord>}
|
||||
*/
|
||||
prepareForRendering(supportedTypes, keepInternalCopy = true) {
|
||||
// if not internal copy and we have no data, bypass rendering
|
||||
if (!this.loaded) {
|
||||
prepareForRendering(drawerId, supportedTypes, keepInternalCopy = true, _shareTileUpdateStamp = null) {
|
||||
|
||||
// Locked update of render target,
|
||||
if (_shareTileUpdateStamp) {
|
||||
for (let tile of this._tiles) {
|
||||
if (tile.processing === _shareTileUpdateStamp) {
|
||||
tile.updateRenderTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if not internal copy and we have no data, or we are ready to render, exit
|
||||
if (!this.loaded || supportedTypes.includes(this.type)) {
|
||||
return $.Promise.resolve(this);
|
||||
}
|
||||
|
||||
@ -224,57 +249,71 @@
|
||||
}
|
||||
|
||||
// we can get here only if we want to render incompatible type
|
||||
let internalCache = this[DRAWER_INTERNAL_CACHE] = new $.SimpleCacheRecord();
|
||||
let internalCache = this[DRAWER_INTERNAL_CACHE];
|
||||
if (!internalCache) {
|
||||
internalCache = this[DRAWER_INTERNAL_CACHE] = {};
|
||||
}
|
||||
|
||||
internalCache = internalCache[drawerId];
|
||||
if (internalCache) {
|
||||
// already done
|
||||
return $.Promise.resolve(this);
|
||||
} else {
|
||||
internalCache = this[DRAWER_INTERNAL_CACHE][drawerId] = new $.SimpleCacheRecord();
|
||||
}
|
||||
const conversionPath = $.convertor.getConversionPath(this.type, supportedTypes);
|
||||
if (!conversionPath) {
|
||||
$.console.error(`[getDataForRendering] Conversion conversion ${this.type} ---> ${supportedTypes} cannot be done!`);
|
||||
$.console.error(`[getDataForRendering] Conversion ${this.type} ---> ${supportedTypes} cannot be done!`);
|
||||
return $.Promise.resolve(this);
|
||||
}
|
||||
internalCache.withTileReference(this._tRef);
|
||||
const selectedFormat = conversionPath[conversionPath.length - 1].target.value;
|
||||
return $.convertor.convert(this._tRef, this.data, this.type, selectedFormat).then(data => {
|
||||
internalCache.setDataAs(data, selectedFormat);
|
||||
return internalCache;
|
||||
return this;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform cache to desired type and get the data after conversion.
|
||||
* Does nothing if the type equals to the current type. Asynchronous.
|
||||
* Transformation is LAZY, meaning conversions are performed only to
|
||||
* match the last conversion request target type.
|
||||
* @param {string|[string]} type if array provided, the system will
|
||||
* try to optimize for the best type to convert to.
|
||||
* @return {OpenSeadragon.Promise<?>}
|
||||
*/
|
||||
transformTo(type = this._type) {
|
||||
if (!this.loaded ||
|
||||
type !== this._type ||
|
||||
(Array.isArray(type) && !type.includes(this._type))) {
|
||||
if (!this.loaded) {
|
||||
this._conversionJobQueue = this._conversionJobQueue || [];
|
||||
let resolver = null;
|
||||
const promise = new $.Promise((resolve, reject) => {
|
||||
resolver = resolve;
|
||||
});
|
||||
|
||||
if (!this.loaded) {
|
||||
this._conversionJobQueue = this._conversionJobQueue || [];
|
||||
let resolver = null;
|
||||
const promise = new $.Promise((resolve, reject) => {
|
||||
resolver = resolve;
|
||||
});
|
||||
this._conversionJobQueue.push(() => {
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
//must re-check types since we perform in a queue of conversion requests
|
||||
if (type !== this._type || (Array.isArray(type) && !type.includes(this._type))) {
|
||||
//ensures queue gets executed after finish
|
||||
this._convert(this._type, type);
|
||||
this._promise.then(data => resolver(data));
|
||||
} else {
|
||||
//must ensure manually, but after current promise finished, we won't wait for the following job
|
||||
this._promise.then(data => {
|
||||
this._checkAwaitsConvert();
|
||||
return resolver(data);
|
||||
});
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
// Todo consider submitting only single tranform job to queue: any other transform calls will have
|
||||
// no effect, the last one decides the target format
|
||||
this._conversionJobQueue.push(() => {
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
//must re-check types since we perform in a queue of conversion requests
|
||||
if (type !== this._type || (Array.isArray(type) && !type.includes(this._type))) {
|
||||
//ensures queue gets executed after finish
|
||||
this._convert(this._type, type);
|
||||
this._promise.then(data => resolver(data));
|
||||
} else {
|
||||
//must ensure manually, but after current promise finished, we won't wait for the following job
|
||||
this._promise.then(data => {
|
||||
this._checkAwaitsConvert();
|
||||
return resolver(data);
|
||||
});
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
if (type !== this._type || (Array.isArray(type) && !type.includes(this._type))) {
|
||||
this._convert(this._type, type);
|
||||
}
|
||||
return this._promise;
|
||||
@ -287,7 +326,9 @@
|
||||
destroyInternalCache() {
|
||||
const internal = this[DRAWER_INTERNAL_CACHE];
|
||||
if (internal) {
|
||||
internal.destroy();
|
||||
for (let iCache in internal) {
|
||||
internal[iCache].destroy();
|
||||
}
|
||||
delete this[DRAWER_INTERNAL_CACHE];
|
||||
}
|
||||
}
|
||||
@ -360,18 +401,16 @@
|
||||
}
|
||||
$.console.assert(tile, '[CacheRecord.addTile] tile is required');
|
||||
|
||||
//allow overriding the cache - existing tile or different type
|
||||
if (this._tiles.includes(tile)) {
|
||||
this.removeTile(tile);
|
||||
|
||||
} else if (!this.loaded) {
|
||||
// first come first served, data for existing tiles is NOT overridden
|
||||
if (this._tiles.length < 1) {
|
||||
this._type = type;
|
||||
this._promise = $.Promise.resolve(data);
|
||||
this._data = data;
|
||||
this.loaded = true;
|
||||
this._tiles.push(tile);
|
||||
} else if (!this._tiles.includes(tile)) {
|
||||
this._tiles.push(tile);
|
||||
}
|
||||
//else pass: the tile data type will silently change as it inherits this cache
|
||||
this._tiles.push(tile);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -446,32 +485,44 @@
|
||||
return $.Promise.resolve();
|
||||
}
|
||||
if (this.loaded) {
|
||||
// No-op if attempt to replace with the same object
|
||||
if (this._data === data && this._type === type) {
|
||||
return this._promise;
|
||||
}
|
||||
$.convertor.destroy(this._data, this._type);
|
||||
this._type = type;
|
||||
this._data = data;
|
||||
this._promise = $.Promise.resolve(data);
|
||||
const internal = this[DRAWER_INTERNAL_CACHE];
|
||||
if (internal) {
|
||||
// TODO: if update will be greedy uncomment (see below)
|
||||
//internal.withTileReference(this._tRef);
|
||||
internal.setDataAs(data, type);
|
||||
for (let iCache in internal) {
|
||||
// TODO: if update will be greedy uncomment (see below)
|
||||
//internal[iCache].withTileReference(this._tRef);
|
||||
internal[iCache].setDataAs(data, type);
|
||||
}
|
||||
}
|
||||
this._triggerNeedsDraw();
|
||||
return this._promise;
|
||||
}
|
||||
return this._promise.then(x => {
|
||||
$.convertor.destroy(x, this._type);
|
||||
return this._promise.then(() => {
|
||||
// No-op if attempt to replace with the same object
|
||||
if (this._data === data && this._type === type) {
|
||||
return this._data;
|
||||
}
|
||||
$.convertor.destroy(this._data, this._type);
|
||||
this._type = type;
|
||||
this._data = data;
|
||||
this._promise = $.Promise.resolve(data);
|
||||
const internal = this[DRAWER_INTERNAL_CACHE];
|
||||
if (internal) {
|
||||
// TODO: if update will be greedy uncomment (see below)
|
||||
//internal.withTileReference(this._tRef);
|
||||
internal.setDataAs(data, type);
|
||||
for (let iCache in internal) {
|
||||
// TODO: if update will be greedy uncomment (see below)
|
||||
//internal[iCache].withTileReference(this._tRef);
|
||||
internal[iCache].setDataAs(data, type);
|
||||
}
|
||||
}
|
||||
this._triggerNeedsDraw();
|
||||
return x;
|
||||
return this._data;
|
||||
});
|
||||
}
|
||||
|
||||
@ -485,7 +536,7 @@
|
||||
const convertor = $.convertor,
|
||||
conversionPath = convertor.getConversionPath(from, to);
|
||||
if (!conversionPath) {
|
||||
$.console.error(`[CacheRecord._convert] Conversion conversion ${from} ---> ${to} cannot be done!`);
|
||||
$.console.error(`[CacheRecord._convert] Conversion ${from} ---> ${to} cannot be done!`);
|
||||
return; //no-op
|
||||
}
|
||||
|
||||
@ -576,7 +627,7 @@
|
||||
const convertor = $.convertor,
|
||||
conversionPath = convertor.getConversionPath(this._type, type);
|
||||
if (!conversionPath) {
|
||||
$.console.error(`[SimpleCacheRecord.transformTo] Conversion conversion ${this._type} ---> ${type} cannot be done!`);
|
||||
$.console.error(`[SimpleCacheRecord.transformTo] Conversion ${this._type} ---> ${type} cannot be done!`);
|
||||
return $.Promise.resolve(); //no-op
|
||||
}
|
||||
|
||||
@ -630,14 +681,6 @@
|
||||
this._type = type;
|
||||
this._data = data;
|
||||
this.loaded = true;
|
||||
// TODO: if done greedily, we transform each plugin set call
|
||||
// pros: we can show midresults
|
||||
// cons: unecessary work
|
||||
// might be solved by introducing explicit tile update pipeline (already attemps)
|
||||
// --> flag that knows which update is last
|
||||
// if (this.format && !this.format.includes(type)) {
|
||||
// this.transformTo(this.format);
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
@ -685,7 +728,7 @@
|
||||
* the number of images below that number. Note, as well, that even the number of images
|
||||
* may temporarily surpass that number, but should eventually come back down to the max specified.
|
||||
* @private
|
||||
* @param {Object} options - Tile info.
|
||||
* @param {Object} options - Cache creation parameters.
|
||||
* @param {OpenSeadragon.Tile} options.tile - The tile to cache.
|
||||
* @param {?String} [options.cacheKey=undefined] - Cache Key to use. Defaults to options.tile.cacheKey
|
||||
* @param {String} options.tile.cacheKey - The unique key used to identify this tile in the cache.
|
||||
@ -704,9 +747,13 @@
|
||||
$.console.assert( theTile, "[TileCache.cacheTile] options.tile is required" );
|
||||
$.console.assert( theTile.cacheKey, "[TileCache.cacheTile] options.tile.cacheKey is required" );
|
||||
|
||||
let cutoff = options.cutoff || 0,
|
||||
insertionIndex = this._tilesLoaded.length,
|
||||
cacheKey = options.cacheKey || theTile.cacheKey;
|
||||
if (options.image instanceof Image) {
|
||||
$.console.warn("[TileCache.cacheTile] options.image is deprecated!" );
|
||||
options.data = options.image;
|
||||
options.dataType = "image";
|
||||
}
|
||||
|
||||
let cacheKey = options.cacheKey || theTile.cacheKey;
|
||||
|
||||
let cacheRecord = this._cachesLoaded[cacheKey] || this._zombiesLoaded[cacheKey];
|
||||
if (!cacheRecord) {
|
||||
@ -717,8 +764,8 @@
|
||||
}
|
||||
|
||||
//allow anything but undefined, null, false (other values mean the data was set, for example '0')
|
||||
$.console.assert( options.data !== undefined && options.data !== null && options.data !== false,
|
||||
"[TileCache.cacheTile] options.data is required to create an CacheRecord" );
|
||||
const validData = options.data !== undefined && options.data !== null && options.data !== false;
|
||||
$.console.assert( validData, "[TileCache.cacheTile] options.data is required to create an CacheRecord" );
|
||||
cacheRecord = this._cachesLoaded[cacheKey] = new $.CacheRecord();
|
||||
this._cachesLoadedCount++;
|
||||
} else if (cacheRecord._destroyed) {
|
||||
@ -738,9 +785,151 @@
|
||||
theTile.tiledImage._needsDraw = true;
|
||||
}
|
||||
|
||||
this._freeOldRecordRoutine(theTile, options.cutoff || 0);
|
||||
return cacheRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes cache key
|
||||
* @private
|
||||
* @param {Object} options - Cache creation parameters.
|
||||
* @param {String} options.oldCacheKey - Current key
|
||||
* @param {String} options.newCacheKey - New key to set
|
||||
* @return {OpenSeadragon.CacheRecord | null}
|
||||
*/
|
||||
renameCache( options ) {
|
||||
let originalCache = this._cachesLoaded[options.oldCacheKey];
|
||||
const newKey = options.newCacheKey,
|
||||
oldKey = options.oldCacheKey;
|
||||
|
||||
if (!originalCache) {
|
||||
originalCache = this._zombiesLoaded[oldKey];
|
||||
$.console.assert( originalCache, "[TileCache.renameCache] oldCacheKey must reference existing cache!" );
|
||||
if (this._zombiesLoaded[newKey]) {
|
||||
$.console.error("Cannot rename zombie cache %s to %s: the target cache is occupied!",
|
||||
oldKey, newKey);
|
||||
return null;
|
||||
}
|
||||
this._zombiesLoaded[newKey] = originalCache;
|
||||
delete this._zombiesLoaded[oldKey];
|
||||
} else if (this._cachesLoaded[newKey]) {
|
||||
$.console.error("Cannot rename cache %s to %s: the target cache is occupied!",
|
||||
oldKey, newKey);
|
||||
return null; // do not remove, we perform additional fixes on caches later on when swap occurred
|
||||
} else {
|
||||
this._cachesLoaded[newKey] = originalCache;
|
||||
delete this._cachesLoaded[oldKey];
|
||||
}
|
||||
|
||||
for (let tile of originalCache._tiles) {
|
||||
tile.reflectCacheRenamed(oldKey, newKey);
|
||||
}
|
||||
|
||||
// do not call free old record routine, we did not increase cache size
|
||||
return originalCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a cache if it exists and creates a new copy of a target, different cache if it does not
|
||||
* @private
|
||||
* @param {Object} options
|
||||
* @param {OpenSeadragon.Tile} options.tile - The tile to own ot add record for the cache.
|
||||
* @param {String} options.copyTargetKey - The unique key used to identify this tile in the cache.
|
||||
* @param {String} options.newCacheKey - The unique key the copy will be created for.
|
||||
* @param {String} [options.desiredType=undefined] - For optimization purposes, the desired type. Can
|
||||
* be ignored.
|
||||
* @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
|
||||
* tiles will not be released.
|
||||
* @returns {OpenSeadragon.Promise<OpenSeadragon.CacheRecord>} - New record.
|
||||
*/
|
||||
cloneCache(options) {
|
||||
const theTile = options.tile;
|
||||
const cacheKey = options.copyTargetKey;
|
||||
//todo consider zombie drop support and custom queue for working cache items only
|
||||
const cacheRecord = this._cachesLoaded[cacheKey] || this._zombiesLoaded[cacheKey];
|
||||
$.console.assert(cacheRecord, "[TileCache.cloneCache] attempt to clone non-existent cache %s!", cacheKey);
|
||||
$.console.assert(!this._cachesLoaded[options.newCacheKey],
|
||||
"[TileCache.cloneCache] attempt to copy clone to existing cache %s!", options.newCacheKey);
|
||||
|
||||
const desiredType = options.desiredType || undefined;
|
||||
return cacheRecord.getDataAs(desiredType, true).then(data => {
|
||||
let newRecord = this._cachesLoaded[options.newCacheKey] = new $.CacheRecord();
|
||||
newRecord.addTile(theTile, data, cacheRecord.type);
|
||||
this._cachesLoadedCount++;
|
||||
this._freeOldRecordRoutine(theTile, options.cutoff || 0);
|
||||
return newRecord;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume cache by another cache
|
||||
* @private
|
||||
* @param {Object} options
|
||||
* @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,
|
||||
* inheriting its tiles and key.
|
||||
* @param {String} options.consumerKey - The cache that consumes the victim. In fact, it gets destroyed and
|
||||
* replaced by victim, which inherits all its metadata.
|
||||
* @param {}
|
||||
*/
|
||||
consumeCache(options) {
|
||||
const victim = this._cachesLoaded[options.victimKey],
|
||||
tile = options.tile;
|
||||
if (!victim || (!tile.loaded && !tile.loading)) {
|
||||
$.console.warn("Attempt to consume non-existent cache: this is probably a bug!");
|
||||
return;
|
||||
}
|
||||
const consumer = this._cachesLoaded[options.consumerKey];
|
||||
let tiles = [...tile.getCache()._tiles];
|
||||
|
||||
if (consumer) {
|
||||
// We need to avoid costly conversions: replace consumer.
|
||||
// unloadCacheForTile() will modify the array, iterate over a copy
|
||||
const iterateTiles = [...consumer._tiles];
|
||||
for (let tile of iterateTiles) {
|
||||
this.unloadCacheForTile(tile, options.consumerKey, true);
|
||||
}
|
||||
}
|
||||
// Just swap victim to become new consumer
|
||||
const resultCache = this.renameCache({
|
||||
oldCacheKey: options.victimKey,
|
||||
newCacheKey: options.consumerKey
|
||||
});
|
||||
|
||||
if (resultCache) {
|
||||
// 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()
|
||||
for (let tile of tiles) {
|
||||
if (tile !== options.tile && tile.loaded) {
|
||||
tile.addCache(options.consumerKey, resultCache.data, resultCache.type, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* This method ensures other tiles are restored if one of the tiles
|
||||
* was requested restore().
|
||||
* @param tile
|
||||
* @param originalCache
|
||||
*/
|
||||
restoreTilesThatShareOriginalCache(tile, originalCache) {
|
||||
for (let t of originalCache._tiles) {
|
||||
// todo a bit dirty, touching tile privates
|
||||
this.unloadCacheForTile(t, t.cacheKey, t.__restoreRequestedFree);
|
||||
delete t._caches[t.cacheKey];
|
||||
t.cacheKey = t.originalCacheKey;
|
||||
}
|
||||
}
|
||||
|
||||
_freeOldRecordRoutine(theTile, cutoff) {
|
||||
let insertionIndex = this._tilesLoaded.length,
|
||||
worstTileIndex = -1;
|
||||
|
||||
// Note that just because we're unloading a tile doesn't necessarily mean
|
||||
// we're unloading its cache records. With repeated calls it should sort itself out, though.
|
||||
let worstTileIndex = -1;
|
||||
if ( this._cachesLoadedCount + this._zombiesLoadedCount > this._maxCacheItemCount ) {
|
||||
//prefer zombie deletion, faster, better
|
||||
if (this._zombiesLoadedCount > 0) {
|
||||
@ -759,7 +948,8 @@
|
||||
|
||||
if ( prevTile.level <= cutoff ||
|
||||
prevTile.beingDrawn ||
|
||||
prevTile.loading ) {
|
||||
prevTile.loading ||
|
||||
prevTile.processing ) {
|
||||
continue;
|
||||
}
|
||||
if ( !worstTile ) {
|
||||
@ -793,8 +983,6 @@
|
||||
//tile is already recorded, do not add tile, but remove the tile at insertion index
|
||||
this._tilesLoaded.splice(insertionIndex, 1);
|
||||
}
|
||||
|
||||
return cacheRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,8 +83,6 @@
|
||||
* Defaults to the setting in {@link OpenSeadragon.Options}.
|
||||
* @param {Object} [options.ajaxHeaders={}]
|
||||
* A set of headers to include when making tile AJAX requests.
|
||||
* @param {Boolean} [options.callTileLoadedWithCachedData]
|
||||
* Invoke tile-loded event for also for tiles loaded from cache if true.
|
||||
*/
|
||||
$.TiledImage = function( options ) {
|
||||
this._initialized = false;
|
||||
@ -192,7 +190,6 @@ $.TiledImage = function( options ) {
|
||||
compositeOperation: $.DEFAULT_SETTINGS.compositeOperation,
|
||||
subPixelRoundingForTransparency: $.DEFAULT_SETTINGS.subPixelRoundingForTransparency,
|
||||
maxTilesPerFrame: $.DEFAULT_SETTINGS.maxTilesPerFrame,
|
||||
callTileLoadedWithCachedData: $.DEFAULT_SETTINGS.callTileLoadedWithCachedData
|
||||
}, options );
|
||||
|
||||
this._preload = this.preload;
|
||||
@ -233,8 +230,7 @@ $.TiledImage = function( options ) {
|
||||
this._ownAjaxHeaders = {};
|
||||
this.setAjaxHeaders(ajaxHeaders, false);
|
||||
this._initialized = true;
|
||||
this.invalidatedAt = 0;
|
||||
this.invalidatedFinishAt = 0;
|
||||
// this.invalidatedAt = 0;
|
||||
};
|
||||
|
||||
$.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.TiledImage.prototype */{
|
||||
@ -286,26 +282,17 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
/**
|
||||
* Forces the system consider all tiles in this tiled image
|
||||
* as outdated, and fire tile update event on relevant tiles
|
||||
* Detailed description is available within the 'tile-needs-update'
|
||||
* event. TODO: consider re-using update function instead?
|
||||
* Detailed description is available within the 'tile-invalidated'
|
||||
* event.
|
||||
* @param {boolean} [viewportOnly=false] optionally invalidate only viewport-visible tiles if true
|
||||
* @param {number} [tStamp=OpenSeadragon.now()] optionally provide tStamp of the update event
|
||||
* @param {Boolean} [restoreTiles=true] if true, tile processing starts from the tile original data
|
||||
*/
|
||||
invalidate: function (viewportOnly, tStamp) {
|
||||
requestInvalidate: function (viewportOnly, tStamp, restoreTiles = true) {
|
||||
tStamp = tStamp || $.now();
|
||||
this.invalidatedAt = tStamp; //todo document, or remove by something nicer
|
||||
|
||||
//always invalidate active tiles
|
||||
for (let tile of this.lastDrawn) {
|
||||
$.invalidateTile(tile, this, tStamp, this.viewer);
|
||||
}
|
||||
//if not called from world or not desired, avoid update of offscreen data
|
||||
if (viewportOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tiles = this._tileCache.getLoadedTilesFor(this);
|
||||
$.invalidateTilesLater(tiles, tStamp, this.viewer);
|
||||
// this.invalidatedAt = tStamp; //todo document, or remove by something nicer
|
||||
const tiles = viewportOnly ? this._lastDrawn.map(x => x.tile) : this._tileCache.getLoadedTilesFor(this);
|
||||
this.viewer.world.requestTileInvalidateEvent(tiles, tStamp, restoreTiles);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1821,11 +1808,9 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
levelVisibility
|
||||
);
|
||||
|
||||
if (!tile.loaded && !tile.loading) {
|
||||
// Tile was created or its data removed: check whether cache has the data.
|
||||
// this method sets tile.loading=true if data available, which prevents
|
||||
// job creation later on
|
||||
this._tryFindTileCacheRecord(tile);
|
||||
// Try-find will populate tile with data if equal tile exists in system
|
||||
if (!tile.loaded && !tile.loading && this._tryFindTileCacheRecord(tile)) {
|
||||
loadingCoverage = true;
|
||||
}
|
||||
|
||||
if ( tile.loading ) {
|
||||
@ -1890,28 +1875,33 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
* @param {OpenSeadragon.Tile} tile
|
||||
*/
|
||||
_tryFindTileCacheRecord: function(tile) {
|
||||
if (tile.cacheKey !== tile.originalCacheKey) {
|
||||
//we found original data: this data will be used to re-execute the pipeline
|
||||
let record = this._tileCache.getCacheRecord(tile.originalCacheKey);
|
||||
if (record) {
|
||||
tile.loading = true;
|
||||
tile.loaded = false;
|
||||
this._setTileLoaded(tile, record.data, null, null, record.type);
|
||||
return true;
|
||||
}
|
||||
let record = this._tileCache.getCacheRecord(tile.cacheKey);
|
||||
|
||||
if (!record) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let record = this._tileCache.getCacheRecord(tile.cacheKey);
|
||||
if (record) {
|
||||
// setup without calling tile loaded event! tile cache is ready for usage,
|
||||
tile.loading = true;
|
||||
tile.loaded = false;
|
||||
// we could send null as data (cache not re-created), but deprecated events access the data
|
||||
this._setTileLoaded(tile, record.data, null, null, record.type,
|
||||
this.callTileLoadedWithCachedData);
|
||||
return true;
|
||||
// if we find existing record, check the original data of existing tile of this record
|
||||
let baseTile = record._tiles[0];
|
||||
if (!baseTile) {
|
||||
// we are unable to setup the tile, this might be a bug somewhere else
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
// 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;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -2112,9 +2102,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
* @param {?Number} cutoff ignored, @deprecated
|
||||
* @param {?XMLHttpRequest} tileRequest
|
||||
* @param {?String} [dataType=undefined] data type, derived automatically if not set
|
||||
* @param {?Boolean} [withEvent=true] do not trigger event if true
|
||||
*/
|
||||
_setTileLoaded: function(tile, data, cutoff, tileRequest, dataType, withEvent = true) {
|
||||
_setTileLoaded: function(tile, data, cutoff, tileRequest, dataType) {
|
||||
tile.tiledImage = this; //unloaded with tile.unload(), so we need to set it back
|
||||
// does nothing if tile.cacheKey already present
|
||||
tile.addCache(tile.cacheKey, data, dataType, false, false);
|
||||
@ -2136,6 +2125,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
tile.hasTransparency = tile.hasTransparency || _this.source.hasTransparency(
|
||||
undefined, tile.getUrl(), tile.ajaxHeaders, tile.postData
|
||||
);
|
||||
tile.updateRenderTarget();
|
||||
//make sure cache data is ready for drawing, if not, request the desired format
|
||||
const cache = tile.getCache(tile.cacheKey),
|
||||
requiredTypes = _this._drawer.getSupportedDataFormats();
|
||||
@ -2144,7 +2134,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
resolver(tile);
|
||||
} else if (!requiredTypes.includes(cache.type)) {
|
||||
//initiate conversion as soon as possible if incompatible with the drawer
|
||||
cache.prepareForRendering(requiredTypes, _this._drawer.options.usePrivateCache).then(cacheRef => {
|
||||
cache.prepareForRendering(_this._drawer.getId(), requiredTypes, _this._drawer.options.usePrivateCache).then(cacheRef => {
|
||||
if (!cacheRef) {
|
||||
return cache.transformTo(requiredTypes);
|
||||
}
|
||||
@ -2171,10 +2161,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
|
||||
}
|
||||
|
||||
const fallbackCompletion = getCompletionCallback();
|
||||
if (!withEvent) {
|
||||
fallbackCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when a tile has just been loaded in memory. That means that the
|
||||
|
@ -726,6 +726,8 @@ $.TileSource.prototype = {
|
||||
* particularly if you want to use empty TiledImage with client-side derived data
|
||||
* only. The default tile-cache key is then called "" - an empty string.
|
||||
*
|
||||
* todo AIOSA: provide another hash function that maps data onto tiles 1:1 (e.g sobel) or 1:m (vignetting)
|
||||
*
|
||||
* Note: default behaviour does not take into account post data.
|
||||
* @param {Number} level tile level it was fetched with
|
||||
* @param {Number} x x-coordinate in the pyramid level
|
||||
|
@ -762,6 +762,27 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates data within every tile in the viewer. Should be called
|
||||
* when tiles are outdated and should be re-processed. Useful mainly
|
||||
* for plugins that change tile data.
|
||||
* @function
|
||||
* @param {Boolean} [restoreTiles=true] if true, tile processing starts from the tile original data
|
||||
* @fires OpenSeadragon.Viewer.event:tile-invalidated
|
||||
*/
|
||||
requestInvalidate: function (restoreTiles = true) {
|
||||
if ( !THIS[ this.hash ] ) {
|
||||
//this viewer has already been destroyed: returning immediately
|
||||
return;
|
||||
}
|
||||
|
||||
const tStamp = $.now();
|
||||
this.world.requestInvalidate(tStamp, restoreTiles);
|
||||
if (this.navigator) {
|
||||
this.navigator.world.requestInvalidate(tStamp, restoreTiles);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @function
|
||||
|
@ -99,7 +99,7 @@
|
||||
this._setupRenderer();
|
||||
|
||||
// Unique type per drawer: uploads texture to unique webgl context.
|
||||
this._dataType = `${Date.now()}_TEX_2D`;
|
||||
this._dataType = `${this.getId()}_TEX_2D`;
|
||||
this._supportedFormats = [];
|
||||
this._setupTextureHandlers(this._dataType);
|
||||
|
||||
|
110
src/world.js
110
src/world.js
@ -54,6 +54,7 @@ $.World = function( options ) {
|
||||
this._needsDraw = false;
|
||||
this._autoRefigureSizes = true;
|
||||
this._needsSizesFigured = false;
|
||||
this._queuedInvalidateTiles = [];
|
||||
this._delegatedFigureSizes = function(event) {
|
||||
if (_this._autoRefigureSizes) {
|
||||
_this._figureSizes();
|
||||
@ -235,18 +236,102 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
|
||||
/**
|
||||
* Forces the system consider all tiles across all tiled images
|
||||
* as outdated, and fire tile update event on relevant tiles
|
||||
* Detailed description is available within the 'tile-needs-update'
|
||||
* Detailed description is available within the 'tile-invalidated'
|
||||
* event.
|
||||
* @param {number} [tStamp=OpenSeadragon.now()] optionally provide tStamp of the update event
|
||||
* @param {Boolean} [restoreTiles=true] if true, tile processing starts from the tile original data
|
||||
* @function
|
||||
* @fires OpenSeadragon.Viewer.event:tile-invalidated
|
||||
*/
|
||||
invalidateItems: function () {
|
||||
const updatedAt = $.now();
|
||||
$.__updated = updatedAt;
|
||||
requestInvalidate: function (tStamp, restoreTiles = true) {
|
||||
$.__updated = tStamp = tStamp || $.now();
|
||||
for ( let i = 0; i < this._items.length; i++ ) {
|
||||
this._items[i].invalidate(true, updatedAt);
|
||||
this._items[i].requestInvalidate(true, tStamp, restoreTiles);
|
||||
}
|
||||
|
||||
const tiles = this.viewer.tileCache.getLoadedTilesFor(true);
|
||||
$.invalidateTilesLater(tiles, updatedAt, this.viewer);
|
||||
// Delay processing of all tiles of all items to a later stage by increasing tstamp
|
||||
this.requestTileInvalidateEvent(tiles, tStamp, restoreTiles);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requests tile data update.
|
||||
* @function OpenSeadragon.Viewer.prototype._updateSequenceButtons
|
||||
* @private
|
||||
* @param {Array<OpenSeadragon.Tile>} tileList tiles to update
|
||||
* @param {Number} tStamp timestamp in milliseconds, if active timestamp of the same value is executing,
|
||||
* changes are added to the cycle, else they await next iteration
|
||||
* @param {Boolean} [restoreTiles=true] if true, tile processing starts from the tile original data
|
||||
* @fires OpenSeadragon.Viewer.event:tile-invalidated
|
||||
*/
|
||||
requestTileInvalidateEvent: function(tileList, tStamp, restoreTiles = true) {
|
||||
if (tileList.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._queuedInvalidateTiles.length) {
|
||||
this._queuedInvalidateTiles.push(tileList);
|
||||
return;
|
||||
}
|
||||
|
||||
// this.viewer.viewer is defined in navigator, ensure we call event on the parent viewer
|
||||
const eventTarget = this.viewer.viewer || this.viewer;
|
||||
const finish = () => {
|
||||
for (let tile of tileList) {
|
||||
// pass update stamp on the new cache object to avoid needless updates
|
||||
const newCache = tile.getCache();
|
||||
if (newCache) {
|
||||
|
||||
newCache._updateStamp = tStamp;
|
||||
for (let t of newCache._tiles) {
|
||||
// Mark all as processing
|
||||
t.processing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this._queuedInvalidateTiles.length) {
|
||||
// Make space for other logics execution before we continue in processing
|
||||
let list = this._queuedInvalidateTiles.splice(0, 1)[0];
|
||||
this.requestTileInvalidateEvent(list, tStamp, restoreTiles);
|
||||
} else {
|
||||
this.draw();
|
||||
}
|
||||
};
|
||||
|
||||
const supportedFormats = eventTarget.drawer.getSupportedDataFormats();
|
||||
const keepInternalCacheCopy = eventTarget.drawer.options.usePrivateCache;
|
||||
const drawerId = eventTarget.drawer.getId();
|
||||
|
||||
tileList = tileList.filter(tile => {
|
||||
if (!tile.loaded || tile.processing) {
|
||||
return false;
|
||||
}
|
||||
const tileCache = tile.getCache();
|
||||
if (tileCache._updateStamp >= tStamp) {
|
||||
return false;
|
||||
}
|
||||
tileCache._updateStamp = tStamp;
|
||||
|
||||
for (let t of tileCache._tiles) {
|
||||
// Mark all as processing
|
||||
t.processing = true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
$.Promise.all(tileList.map(tile => {
|
||||
tile.AAAAAAA = new Date().toISOString();
|
||||
if (restoreTiles) {
|
||||
tile.restore();
|
||||
}
|
||||
return eventTarget.raiseEventAwaiting('tile-invalidated', {
|
||||
tile: tile,
|
||||
tiledImage: tile.tiledImage,
|
||||
}).then(() => {
|
||||
tile.updateRenderTargetWithDataTransform(drawerId, supportedFormats, keepInternalCacheCopy);
|
||||
});
|
||||
})).catch(finish).then(finish);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -277,14 +362,11 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
|
||||
* Draws all items.
|
||||
*/
|
||||
draw: function() {
|
||||
return new $.Promise((resolve) => {
|
||||
this.viewer.drawer.draw(this._items);
|
||||
this._needsDraw = false;
|
||||
for (let item of this._items) {
|
||||
this._needsDraw = item.setDrawn() || this._needsDraw;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
this.viewer.drawer.draw(this._items);
|
||||
this._needsDraw = false;
|
||||
for (let item of this._items) {
|
||||
this._needsDraw = item.setDrawn() || this._needsDraw;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -145,6 +145,11 @@ const viewer = window.viewer = new OpenSeadragon({
|
||||
tileSources: targetSource,
|
||||
crossOriginPolicy: 'Anonymous',
|
||||
drawer: switcher.activeImplementation("drawer"),
|
||||
showNavigator: true,
|
||||
wrapHorizontal: true,
|
||||
gestureSettingsMouse: {
|
||||
clickToZoom: false
|
||||
}
|
||||
});
|
||||
|
||||
$("#image-select")
|
||||
@ -785,3 +790,55 @@ window.debugCache = function () {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Monitoring of tiles:
|
||||
let monitoredTile = null;
|
||||
async function updateCanvas(node, tile, targetCacheKey) {
|
||||
const data = await tile.getCache(targetCacheKey)?.getDataAs('context2d', true);
|
||||
if (!data) {
|
||||
const text = document.createElement("span");
|
||||
text.innerHTML = targetCacheKey + "<br> empty";
|
||||
node.replaceChildren(text);
|
||||
} else {
|
||||
node.replaceChildren(data.canvas);
|
||||
}
|
||||
}
|
||||
async function processTile(tile) {
|
||||
console.log("Selected tile", tile);
|
||||
await Promise.all([
|
||||
updateCanvas(document.getElementById("tile-original"), tile, tile.originalCacheKey),
|
||||
updateCanvas(document.getElementById("tile-working"), tile, tile._wcKey),
|
||||
updateCanvas(document.getElementById("tile-main"), tile, tile.cacheKey),
|
||||
]);
|
||||
}
|
||||
viewer.addHandler('tile-invalidated', async event => {
|
||||
if (event.tile === monitoredTile) {
|
||||
await processTile(monitoredTile);
|
||||
}
|
||||
}, null, -Infinity); // as a last handler
|
||||
|
||||
// When testing code, you can call in OSD $.debugTile(message, tile) and it will log only for selected tiles on the canvas
|
||||
OpenSeadragon.debugTile = function (msg, t) {
|
||||
if (monitoredTile && monitoredTile.x === t.x && monitoredTile.y === t.y && monitoredTile.level === t.level) {
|
||||
console.log(msg, t);
|
||||
}
|
||||
}
|
||||
|
||||
viewer.addHandler("canvas-release", e => {
|
||||
const tiledImage = viewer.world.getItemAt(viewer.world.getItemCount()-1);
|
||||
if (!tiledImage) {
|
||||
monitoredTile = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const position = viewer.viewport.windowToViewportCoordinates(e.position);
|
||||
|
||||
let tiles = tiledImage._lastDrawn;
|
||||
for (let i = 0; i < tiles.length; i++) {
|
||||
if (tiles[i].tile.bounds.containsPoint(position)) {
|
||||
monitoredTile = tiles[i].tile;
|
||||
return processTile(monitoredTile);
|
||||
}
|
||||
}
|
||||
monitoredTile = null;
|
||||
});
|
||||
|
@ -68,6 +68,16 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="monitoring">
|
||||
Monitoring of a tile lifecycle: (use filters and click on a tile to start monitoring)
|
||||
|
||||
<div style="display: flex">
|
||||
<div id="tile-original"></div>
|
||||
<div id="tile-working"></div>
|
||||
<div id="tile-main"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script src="demo.js"></script>
|
||||
|
||||
<!-- Google analytics -->
|
||||
|
@ -55,7 +55,7 @@
|
||||
this.viewer = options.viewer;
|
||||
|
||||
this.viewer.addHandler('tile-loaded', tileLoadedHandler);
|
||||
this.viewer.addHandler('tile-needs-update', tileUpdateHandler);
|
||||
this.viewer.addHandler('tile-invalidated', tileUpdateHandler);
|
||||
|
||||
// filterIncrement allows to determine whether a tile contains the
|
||||
// latest filters results.
|
||||
@ -82,14 +82,11 @@
|
||||
const processors = getFiltersProcessors(self, tiledImage);
|
||||
|
||||
if (processors.length === 0) {
|
||||
//restore the original data
|
||||
const context = await tile.getOriginalData('context2d', true);
|
||||
tile.setData(context, 'context2d');
|
||||
tile._filterIncrement = self.filterIncrement;
|
||||
return;
|
||||
}
|
||||
|
||||
const contextCopy = await tile.getOriginalData('context2d', true);
|
||||
const contextCopy = await tile.getData('context2d');
|
||||
const currentIncrement = self.filterIncrement;
|
||||
for (let i = 0; i < processors.length; i++) {
|
||||
if (self.filterIncrement !== currentIncrement) {
|
||||
@ -97,6 +94,7 @@
|
||||
}
|
||||
await processors[i](contextCopy);
|
||||
}
|
||||
|
||||
tile._filterIncrement = self.filterIncrement;
|
||||
await tile.setData(contextCopy, 'context2d');
|
||||
}
|
||||
@ -116,7 +114,7 @@
|
||||
filter.processors : [filter.processors];
|
||||
}
|
||||
instance.filterIncrement++;
|
||||
instance.viewer.world.invalidateItems();
|
||||
instance.viewer.requestInvalidate();
|
||||
}
|
||||
|
||||
function getFiltersProcessors(instance, item) {
|
||||
|
@ -188,6 +188,7 @@
|
||||
// do not hold circular references.
|
||||
const circularOSDReferences = {
|
||||
'Tile': 'tiledImage',
|
||||
'CacheRecord': ['_tRef', '_tiles'],
|
||||
'World': 'viewer',
|
||||
'DrawerBase': ['viewer', 'viewport'],
|
||||
'CanvasDrawer': ['viewer', 'viewport'],
|
||||
|
@ -231,8 +231,8 @@
|
||||
tile12.addCache(tile12.cacheKey, 0, T_A, false, false);
|
||||
|
||||
const collideGetSet = async (tile, type) => {
|
||||
const value = await tile.getData(type, false);
|
||||
await tile.setData(value, type, false);
|
||||
const value = await tile.getData(type);
|
||||
await tile.setData(value, type);
|
||||
return value;
|
||||
};
|
||||
|
||||
@ -251,39 +251,40 @@
|
||||
const c12 = tile12.getCache(tile12.cacheKey);
|
||||
|
||||
//test get/set data A
|
||||
let value = await tile00.getData(undefined, false);
|
||||
let value = await tile00.getData(T_A);
|
||||
test.equal(typeAtoB, 0, "No conversion happened when requesting default type data.");
|
||||
test.equal(value, 0, "No conversion, no increase in value A.");
|
||||
test.equal(value, 1, "One copy happened: getData creates working cache -> copy.");
|
||||
//explicit type
|
||||
value = await tile00.getData(T_A, false);
|
||||
value = await tile00.getData(T_A);
|
||||
test.equal(typeAtoB, 0, "No conversion also for tile sharing the cache.");
|
||||
test.equal(value, 0, "Again, no increase in value A.");
|
||||
test.equal(value, 1, "No increase in value A, working cache initialized.");
|
||||
|
||||
//copy & set type A
|
||||
value = await tile00.getData(T_A, true);
|
||||
value = await tile00.getData(T_A);
|
||||
test.equal(typeAtoB, 0, "No conversion also for tile sharing the cache.");
|
||||
test.equal(copyA, 1, "A copy happened.");
|
||||
test.equal(value, 1, "+1 conversion step happened.");
|
||||
await tile00.setData(value, T_A, false); //overwrite
|
||||
await tile00.setData(value, T_A); //overwrite
|
||||
test.equal(tile00.cacheKey, tile00.originalCacheKey, "Overwriting cache: no change in value.");
|
||||
test.equal(c00.type, T_A, "The tile cache data type was unchanged.");
|
||||
//convert to B, async + sync behavior
|
||||
value = await tile00.getData(T_B, false);
|
||||
await tile00.setData(value, T_B, false); //overwrite
|
||||
value = await tile00.getData(T_B);
|
||||
await tile00.setData(value, T_B); //overwrite
|
||||
test.equal(typeAtoB, 1, "Conversion A->B happened.");
|
||||
test.equal(value, 2, "+1 conversion step happened.");
|
||||
//shares cache with tile12 (overwrite=false)
|
||||
value = await tile12.getData(T_B, false);
|
||||
test.equal(typeAtoB, 1, "Conversion A->B happened only once.");
|
||||
test.equal(value, 2, "Value did not change.");
|
||||
// shares cache, but it is different tile instance
|
||||
|
||||
value = await tile12.getData(T_B);
|
||||
test.equal(typeAtoB, 2, "Conversion A->B happened second time -> working cache forcefully initiated over shared data.");
|
||||
test.equal(value, 1, "Original data is 1 since all previous modifications happened over working cache of tile00.");
|
||||
|
||||
//test ASYNC get data
|
||||
value = await tile12.getData(T_B);
|
||||
await tile12.setData(value, T_B, false); //overwrite
|
||||
test.equal(typeAtoB, 1, "No conversion happened when requesting default type data.");
|
||||
await tile12.setData(value, T_B); //overwrite
|
||||
test.equal(typeAtoB, 2, "Two working caches created, two conversions.");
|
||||
test.equal(typeBtoC, 0, "No conversion happened when requesting default type data.");
|
||||
test.equal(copyB, 1, "B type copied.");
|
||||
test.equal(value, 3, "Copy, increase in value type B.");
|
||||
test.equal(copyB, 0, "B type not copied, working cache already initialized.");
|
||||
test.equal(value, 1, "Data stayed the same.");
|
||||
|
||||
// Async collisions testing
|
||||
|
||||
@ -295,94 +296,100 @@
|
||||
tile12.getData(T_A); // B -> C -> A
|
||||
tile12.getData(T_B); // no conversion, all run at the same time
|
||||
value = await tile12.getData(T_A); // B -> C -> A
|
||||
test.equal(typeAtoB, 1, "No conversion A->B.");
|
||||
test.equal(typeAtoB, 2, "No conversion A->B.");
|
||||
test.equal(typeBtoC, 3, "Conversion B->C happened three times.");
|
||||
test.equal(typeCtoA, 3, "Conversion C->A happened three times.");
|
||||
test.equal(typeDtoA, 0, "Conversion D->A did not happen.");
|
||||
test.equal(typeCtoE, 0, "Conversion C->E did not happen.");
|
||||
test.equal(value, 5, "+2 conversion step happened, other conversion steps are copies discarded " +
|
||||
"(get data does not modify cache).");
|
||||
test.equal(value, 3, "We started from value 1 (wokring cache state), and performed two conversions (B->C->A). " +
|
||||
"Any other conversion attempt results were thrown away, cache state does not get updated when conversion takes place, data is copied (by default).");
|
||||
|
||||
//but direct requests on cache change await
|
||||
// C12 cache is still type A, we modified wokring cache!
|
||||
//but direct requests on cache change await all modifications, but are lazy
|
||||
//convert to A, before that request conversion to A and B several times, should finish accordingly
|
||||
c12.transformTo(T_A); // B -> C -> A
|
||||
c12.transformTo(T_B); // A -> B second time
|
||||
c12.transformTo(T_A); // no-op
|
||||
c12.transformTo(T_B); // A -> B
|
||||
c12.transformTo(T_B); // no-op
|
||||
c12.transformTo(T_A); // B -> C -> A
|
||||
c12.transformTo(T_B); // A -> B third time
|
||||
c12.transformTo(T_B); // A -> B
|
||||
//should finish with next await with 6 steps at this point, add two more and await end
|
||||
value = await c12.transformTo(T_A); // B -> C -> A
|
||||
test.equal(typeAtoB, 3, "Conversion A->B happened three times.");
|
||||
test.equal(typeBtoC, 6, "Conversion B->C happened six times.");
|
||||
test.equal(typeCtoA, 6, "Conversion C->A happened six times.");
|
||||
test.equal(typeAtoB, 4, "Conversion A->B happened two more times, in total 4.");
|
||||
test.equal(typeBtoC, 5, "Conversion B->C happened five (3+2) times.");
|
||||
test.equal(typeCtoA, 5, "Conversion C->A happened five (3+2) times.");
|
||||
test.equal(typeDtoA, 0, "Conversion D->A did not happen.");
|
||||
test.equal(typeCtoE, 0, "Conversion C->E did not happen.");
|
||||
test.equal(value, 11, "5-2+8 conversion step happened (the test above did not save the cache so 3 is value).");
|
||||
await tile12.setData(value, T_B, false); // B -> C -> A
|
||||
test.equal(value, 6, "In total 6 conversions on the cache object.");
|
||||
await tile12.setData(value, T_A);
|
||||
test.equal(c12.data, 6, "In total 6 conversions on the cache object, above set changes working cache.");
|
||||
test.equal(c12.data, 6, "Changing type of working cache fires no conversion, we overwrite cache state.");
|
||||
|
||||
// Get set collide tries to modify the cache
|
||||
collideGetSet(tile12, T_A); // B -> C -> A
|
||||
collideGetSet(tile12, T_B); // no conversion, all run at the same time
|
||||
collideGetSet(tile12, T_B); // no conversion, all run at the same time
|
||||
collideGetSet(tile12, T_A); // B -> C -> A
|
||||
collideGetSet(tile12, T_B); // no conversion, all run at the same time
|
||||
//should finish with next await with 6 steps at this point, add two more and await end
|
||||
value = await collideGetSet(tile12, T_A); // B -> C -> A
|
||||
test.equal(typeAtoB, 3, "Conversion A->B not increased, not needed as all T_B requests resolve immediatelly.");
|
||||
test.equal(typeBtoC, 9, "Conversion B->C happened three times more.");
|
||||
test.equal(typeCtoA, 9, "Conversion C->A happened three times more.");
|
||||
test.equal(typeDtoA, 0, "Conversion D->A did not happen.");
|
||||
test.equal(typeCtoE, 0, "Conversion C->E did not happen.");
|
||||
test.equal(value, 13, "11+2 steps (writes are colliding, just single write will happen).");
|
||||
//TODO fix test from here
|
||||
test.ok("TODO: FIX TEST SUITE FOR NEW CACHE SYSTEM");
|
||||
|
||||
//shares cache with tile12
|
||||
value = await tile00.getData(T_A, false);
|
||||
test.equal(typeAtoB, 3, "Conversion A->B nor triggered.");
|
||||
test.equal(value, 13, "Value did not change.");
|
||||
|
||||
//now set value with keeping origin
|
||||
await tile00.setData(42, T_D, true);
|
||||
test.equal(tile12.originalCacheKey, tile12.cacheKey, "Related tile not affected.");
|
||||
test.equal(tile00.originalCacheKey, tile12.originalCacheKey, "Cache data was modified, original kept.");
|
||||
test.notEqual(tile00.cacheKey, tile12.cacheKey, "Main cache keys changed.");
|
||||
const newCache = tile00.getCache();
|
||||
await newCache.transformTo(T_C);
|
||||
test.equal(typeDtoA, 1, "Conversion D->A happens first time.");
|
||||
test.equal(c12.data, 13, "Original cache value kept");
|
||||
test.equal(c12.type, T_A, "Original cache type kept");
|
||||
test.equal(c12, c00, "The same cache.");
|
||||
|
||||
test.equal(typeAtoB, 4, "Conversion A->B triggered.");
|
||||
test.equal(newCache.type, T_C, "Original cache type kept");
|
||||
test.equal(newCache.data, 45, "42+3 steps happened.");
|
||||
|
||||
//try again change in set data, now the cache gets overwritten
|
||||
await tile00.setData(42, T_B, true);
|
||||
test.equal(newCache.type, T_B, "Reset happened in place.");
|
||||
test.equal(newCache.data, 42, "Reset happened in place.");
|
||||
|
||||
// Overwriting stress test with diff cache (see the same test as above, the same reasoning)
|
||||
collideGetSet(tile00, T_A); // B -> C -> A
|
||||
collideGetSet(tile00, T_B); // no conversion, all run at the same time
|
||||
collideGetSet(tile00, T_B); // no conversion, all run at the same time
|
||||
collideGetSet(tile00, T_A); // B -> C -> A
|
||||
collideGetSet(tile00, T_B); // no conversion, all run at the same time
|
||||
//should finish with next await with 6 steps at this point, add two more and await end
|
||||
value = await collideGetSet(tile00, T_A); // B -> C -> A
|
||||
test.equal(typeAtoB, 4, "Conversion A->B not increased.");
|
||||
test.equal(typeBtoC, 13, "Conversion B->C happened three times more.");
|
||||
//we converted D->C before, that's why C->A is one less
|
||||
test.equal(typeCtoA, 12, "Conversion C->A happened three times more.");
|
||||
test.equal(typeDtoA, 1, "Conversion D->A did not happen.");
|
||||
test.equal(typeCtoE, 0, "Conversion C->E did not happen.");
|
||||
test.equal(value, 44, "+2 writes value (writes collide, just one finishes last).");
|
||||
|
||||
test.equal(c12.data, 13, "Original cache value kept");
|
||||
test.equal(c12.type, T_A, "Original cache type kept");
|
||||
test.equal(c12, c00, "The same cache.");
|
||||
|
||||
//todo test destruction throughout the test above
|
||||
//tile00.unload();
|
||||
// // Get set collide tries to modify the cache
|
||||
// collideGetSet(tile12, T_A); // B -> C -> A
|
||||
// collideGetSet(tile12, T_B); // no conversion, all run at the same time
|
||||
// collideGetSet(tile12, T_B); // no conversion, all run at the same time
|
||||
// collideGetSet(tile12, T_A); // B -> C -> A
|
||||
// collideGetSet(tile12, T_B); // no conversion, all run at the same time
|
||||
// //should finish with next await with 6 steps at this point, add two more and await end
|
||||
// value = await collideGetSet(tile12, T_A); // B -> C -> A
|
||||
// test.equal(typeAtoB, 3, "Conversion A->B not increased, not needed as all T_B requests resolve immediatelly.");
|
||||
// test.equal(typeBtoC, 9, "Conversion B->C happened three times more.");
|
||||
// test.equal(typeCtoA, 9, "Conversion C->A happened three times more.");
|
||||
// test.equal(typeDtoA, 0, "Conversion D->A did not happen.");
|
||||
// test.equal(typeCtoE, 0, "Conversion C->E did not happen.");
|
||||
// test.equal(value, 13, "11+2 steps (writes are colliding, just single write will happen).");
|
||||
//
|
||||
// //shares cache with tile12
|
||||
// value = await tile00.getData(T_A, false);
|
||||
// test.equal(typeAtoB, 3, "Conversion A->B nor triggered.");
|
||||
// test.equal(value, 13, "Value did not change.");
|
||||
//
|
||||
// //now set value with keeping origin
|
||||
// await tile00.setData(42, T_D, true);
|
||||
// test.equal(tile12.originalCacheKey, tile12.cacheKey, "Related tile not affected.");
|
||||
// test.equal(tile00.originalCacheKey, tile12.originalCacheKey, "Cache data was modified, original kept.");
|
||||
// test.notEqual(tile00.cacheKey, tile12.cacheKey, "Main cache keys changed.");
|
||||
// const newCache = tile00.getCache();
|
||||
// await newCache.transformTo(T_C);
|
||||
// test.equal(typeDtoA, 1, "Conversion D->A happens first time.");
|
||||
// test.equal(c12.data, 13, "Original cache value kept");
|
||||
// test.equal(c12.type, T_A, "Original cache type kept");
|
||||
// test.equal(c12, c00, "The same cache.");
|
||||
//
|
||||
// test.equal(typeAtoB, 4, "Conversion A->B triggered.");
|
||||
// test.equal(newCache.type, T_C, "Original cache type kept");
|
||||
// test.equal(newCache.data, 45, "42+3 steps happened.");
|
||||
//
|
||||
// //try again change in set data, now the cache gets overwritten
|
||||
// await tile00.setData(42, T_B, true);
|
||||
// test.equal(newCache.type, T_B, "Reset happened in place.");
|
||||
// test.equal(newCache.data, 42, "Reset happened in place.");
|
||||
//
|
||||
// // Overwriting stress test with diff cache (see the same test as above, the same reasoning)
|
||||
// collideGetSet(tile00, T_A); // B -> C -> A
|
||||
// collideGetSet(tile00, T_B); // no conversion, all run at the same time
|
||||
// collideGetSet(tile00, T_B); // no conversion, all run at the same time
|
||||
// collideGetSet(tile00, T_A); // B -> C -> A
|
||||
// collideGetSet(tile00, T_B); // no conversion, all run at the same time
|
||||
// //should finish with next await with 6 steps at this point, add two more and await end
|
||||
// value = await collideGetSet(tile00, T_A); // B -> C -> A
|
||||
// test.equal(typeAtoB, 4, "Conversion A->B not increased.");
|
||||
// test.equal(typeBtoC, 13, "Conversion B->C happened three times more.");
|
||||
// //we converted D->C before, that's why C->A is one less
|
||||
// test.equal(typeCtoA, 12, "Conversion C->A happened three times more.");
|
||||
// test.equal(typeDtoA, 1, "Conversion D->A did not happen.");
|
||||
// test.equal(typeCtoE, 0, "Conversion C->E did not happen.");
|
||||
// test.equal(value, 44, "+2 writes value (writes collide, just one finishes last).");
|
||||
//
|
||||
// test.equal(c12.data, 13, "Original cache value kept");
|
||||
// test.equal(c12.type, T_A, "Original cache type kept");
|
||||
// test.equal(c12, c00, "The same cache.");
|
||||
//
|
||||
// //todo test destruction throughout the test above
|
||||
// //tile00.unload();
|
||||
|
||||
done();
|
||||
})();
|
||||
@ -417,248 +424,257 @@
|
||||
|
||||
//test set/get data in async env
|
||||
(async function() {
|
||||
test.equal(tileCache.numTilesLoaded(), 5, "We loaded 5 tiles");
|
||||
test.equal(tileCache.numCachesLoaded(), 3, "We loaded 3 cache objects");
|
||||
|
||||
const c00 = tile00.getCache(tile00.cacheKey);
|
||||
const c12 = tile12.getCache(tile12.cacheKey);
|
||||
|
||||
//now test multi-cache within tile
|
||||
const theTileKey = tile00.cacheKey;
|
||||
tile00.setData(42, T_E, true);
|
||||
test.ok(tile00.cacheKey !== tile00.originalCacheKey, "Original cache key differs.");
|
||||
test.equal(theTileKey, tile00.originalCacheKey, "Original cache key preserved.");
|
||||
|
||||
//now add artifically another record
|
||||
tile00.addCache("my_custom_cache", 128, T_C);
|
||||
test.equal(tileCache.numTilesLoaded(), 5, "We still loaded only 5 tiles.");
|
||||
test.equal(tileCache.numCachesLoaded(), 5, "The cache has now 5 items.");
|
||||
test.equal(c00.getTileCount(), 2, "The cache still has only two tiles attached.");
|
||||
test.equal(tile00.getCacheSize(), 3, "The tile has three cache objects.");
|
||||
//related tile not really affected
|
||||
test.equal(tile12.cacheKey, tile12.originalCacheKey, "Original cache key not affected elsewhere.");
|
||||
test.equal(tile12.originalCacheKey, theTileKey, "Original cache key also preserved.");
|
||||
test.equal(c12.getTileCount(), 2, "The original data cache still has only two tiles attached.");
|
||||
test.equal(tile12.getCacheSize(), 1, "Related tile cache did not increase.");
|
||||
|
||||
//add and delete cache nothing changes
|
||||
tile00.addCache("my_custom_cache2", 128, T_C);
|
||||
tile00.removeCache("my_custom_cache2");
|
||||
test.equal(tileCache.numTilesLoaded(), 5, "We still loaded only 5 tiles.");
|
||||
test.equal(tileCache.numCachesLoaded(), 5, "The cache has now 5 items.");
|
||||
test.equal(tile00.getCacheSize(), 3, "The tile has three cache objects.");
|
||||
|
||||
//delete cache as a zombie
|
||||
tile00.addCache("my_custom_cache2", 17, T_C);
|
||||
//direct access shoes correct value although we set key!
|
||||
const myCustomCache2Data = tile00.getCache("my_custom_cache2").data;
|
||||
test.equal(myCustomCache2Data, 17, "Previously defined cache does not intervene.");
|
||||
test.equal(tileCache.numCachesLoaded(), 6, "The cache size is 6.");
|
||||
//keep zombie
|
||||
tile00.removeCache("my_custom_cache2", false);
|
||||
test.equal(tileCache.numCachesLoaded(), 6, "The cache is 5 + 1 zombie, no change.");
|
||||
test.equal(tile00.getCacheSize(), 3, "The tile has three cache objects.");
|
||||
|
||||
//revive zombie
|
||||
tile01.addCache("my_custom_cache2", 18, T_C);
|
||||
const myCustomCache2OtherData = tile01.getCache("my_custom_cache2").data;
|
||||
test.equal(myCustomCache2OtherData, myCustomCache2Data, "Caches are equal because revived.");
|
||||
//again, keep zombie
|
||||
tile01.removeCache("my_custom_cache2", false);
|
||||
|
||||
//first create additional cache so zombie is not the youngest
|
||||
tile01.addCache("some weird cache", 11, T_A);
|
||||
test.ok(tile01.cacheKey === tile01.originalCacheKey, "Custom cache does not touch tile cache keys.");
|
||||
|
||||
//insertion aadditional cache clears the zombie first although it is not the youngest one
|
||||
test.equal(tileCache.numCachesLoaded(), 7, "The cache has now 7 items.");
|
||||
|
||||
//Test CAP
|
||||
tileCache._maxCacheItemCount = 7;
|
||||
|
||||
//does not trigger insertion - deletion, since we setData to cache that already exists, 43 value ignored
|
||||
tile12.setData(43, T_B, true);
|
||||
test.notEqual(tile12.cacheKey, tile12.originalCacheKey, "Original cache key differs.");
|
||||
test.equal(theTileKey, tile12.originalCacheKey, "Original cache key preserved.");
|
||||
test.equal(tileCache.numCachesLoaded(), 7, "The cache has still 7 items.");
|
||||
//we called SET DATA with preserve=true on tile12 which was sharing cache with tile00, new cache is also shared
|
||||
test.equal(tile00.originalCacheKey, tile12.originalCacheKey, "Original cache key matches between tiles.");
|
||||
test.equal(tile00.cacheKey, tile12.cacheKey, "Modified cache key matches between tiles.");
|
||||
test.equal(tile12.getCache().data, 42, "The value is not 43 as setData triggers cache share!");
|
||||
|
||||
//triggers insertion - deletion of zombie cache 'my_custom_cache2'
|
||||
tile00.addCache("trigger-max-cache-handler", 5, T_C);
|
||||
//reset CAP
|
||||
tileCache._maxCacheItemCount = OpenSeadragon.DEFAULT_SETTINGS.maxImageCacheCount;
|
||||
|
||||
//try to revive zombie will fail: the zombie was deleted, we will find 18
|
||||
tile01.addCache("my_custom_cache2", 18, T_C);
|
||||
const myCustomCache2RecreatedData = tile01.getCache("my_custom_cache2").data;
|
||||
test.notEqual(myCustomCache2RecreatedData, myCustomCache2Data, "Caches are not equal because created.");
|
||||
test.equal(myCustomCache2RecreatedData, 18, "Cache data is actually as set to 18.");
|
||||
test.equal(tileCache.numCachesLoaded(), 8, "The cache has now 8 items.");
|
||||
|
||||
|
||||
//delete cache bound to other tiles, this tile has 4 caches:
|
||||
// cacheKey: shared, originalCacheKey: shared, <custom cache key>, <custom cache key>
|
||||
// note that cacheKey is shared because we called setData on two items that both create MOD cache
|
||||
tileCache.unloadTile(tile00, true, tileCache._tilesLoaded.indexOf(tile00));
|
||||
test.equal(tileCache.numCachesLoaded(), 6, "The cache has now 8-2 items.");
|
||||
test.equal(tileCache.numTilesLoaded(), 4, "One tile removed.");
|
||||
test.equal(c00.getTileCount(), 1, "The cache has still tile12 left.");
|
||||
|
||||
//now test tile destruction as zombie
|
||||
|
||||
//now test tile cache sharing
|
||||
// TODO FIX
|
||||
test.ok("TODO: FIX TEST SUITE FOR NEW CACHE SYSTEM");
|
||||
done();
|
||||
// test.equal(tileCache.numTilesLoaded(), 5, "We loaded 5 tiles");
|
||||
// test.equal(tileCache.numCachesLoaded(), 3, "We loaded 3 cache objects");
|
||||
//
|
||||
// const c00 = tile00.getCache(tile00.cacheKey);
|
||||
// const c12 = tile12.getCache(tile12.cacheKey);
|
||||
//
|
||||
// //now test multi-cache within tile
|
||||
// const theTileKey = tile00.cacheKey;
|
||||
// tile00.setData(42, T_E, true);
|
||||
// test.ok(tile00.cacheKey !== tile00.originalCacheKey, "Original cache key differs.");
|
||||
// test.equal(theTileKey, tile00.originalCacheKey, "Original cache key preserved.");
|
||||
//
|
||||
// //now add artifically another record
|
||||
// tile00.addCache("my_custom_cache", 128, T_C);
|
||||
// test.equal(tileCache.numTilesLoaded(), 5, "We still loaded only 5 tiles.");
|
||||
// test.equal(tileCache.numCachesLoaded(), 5, "The cache has now 5 items.");
|
||||
// test.equal(c00.getTileCount(), 2, "The cache still has only two tiles attached.");
|
||||
// test.equal(tile00.getCacheSize(), 3, "The tile has three cache objects.");
|
||||
// //related tile not really affected
|
||||
// test.equal(tile12.cacheKey, tile12.originalCacheKey, "Original cache key not affected elsewhere.");
|
||||
// test.equal(tile12.originalCacheKey, theTileKey, "Original cache key also preserved.");
|
||||
// test.equal(c12.getTileCount(), 2, "The original data cache still has only two tiles attached.");
|
||||
// test.equal(tile12.getCacheSize(), 1, "Related tile cache did not increase.");
|
||||
//
|
||||
// //add and delete cache nothing changes
|
||||
// tile00.addCache("my_custom_cache2", 128, T_C);
|
||||
// tile00.removeCache("my_custom_cache2");
|
||||
// test.equal(tileCache.numTilesLoaded(), 5, "We still loaded only 5 tiles.");
|
||||
// test.equal(tileCache.numCachesLoaded(), 5, "The cache has now 5 items.");
|
||||
// test.equal(tile00.getCacheSize(), 3, "The tile has three cache objects.");
|
||||
//
|
||||
// //delete cache as a zombie
|
||||
// tile00.addCache("my_custom_cache2", 17, T_C);
|
||||
// //direct access shoes correct value although we set key!
|
||||
// const myCustomCache2Data = tile00.getCache("my_custom_cache2").data;
|
||||
// test.equal(myCustomCache2Data, 17, "Previously defined cache does not intervene.");
|
||||
// test.equal(tileCache.numCachesLoaded(), 6, "The cache size is 6.");
|
||||
// //keep zombie
|
||||
// tile00.removeCache("my_custom_cache2", false);
|
||||
// test.equal(tileCache.numCachesLoaded(), 6, "The cache is 5 + 1 zombie, no change.");
|
||||
// test.equal(tile00.getCacheSize(), 3, "The tile has three cache objects.");
|
||||
//
|
||||
// //revive zombie
|
||||
// tile01.addCache("my_custom_cache2", 18, T_C);
|
||||
// const myCustomCache2OtherData = tile01.getCache("my_custom_cache2").data;
|
||||
// test.equal(myCustomCache2OtherData, myCustomCache2Data, "Caches are equal because revived.");
|
||||
// //again, keep zombie
|
||||
// tile01.removeCache("my_custom_cache2", false);
|
||||
//
|
||||
// //first create additional cache so zombie is not the youngest
|
||||
// tile01.addCache("some weird cache", 11, T_A);
|
||||
// test.ok(tile01.cacheKey === tile01.originalCacheKey, "Custom cache does not touch tile cache keys.");
|
||||
//
|
||||
// //insertion aadditional cache clears the zombie first although it is not the youngest one
|
||||
// test.equal(tileCache.numCachesLoaded(), 7, "The cache has now 7 items.");
|
||||
//
|
||||
// //Test CAP
|
||||
// tileCache._maxCacheItemCount = 7;
|
||||
//
|
||||
// //does not trigger insertion - deletion, since we setData to cache that already exists, 43 value ignored
|
||||
// tile12.setData(43, T_B, true);
|
||||
// test.notEqual(tile12.cacheKey, tile12.originalCacheKey, "Original cache key differs.");
|
||||
// test.equal(theTileKey, tile12.originalCacheKey, "Original cache key preserved.");
|
||||
// test.equal(tileCache.numCachesLoaded(), 7, "The cache has still 7 items.");
|
||||
// //we called SET DATA with preserve=true on tile12 which was sharing cache with tile00, new cache is also shared
|
||||
// test.equal(tile00.originalCacheKey, tile12.originalCacheKey, "Original cache key matches between tiles.");
|
||||
// test.equal(tile00.cacheKey, tile12.cacheKey, "Modified cache key matches between tiles.");
|
||||
// test.equal(tile12.getCache().data, 42, "The value is not 43 as setData triggers cache share!");
|
||||
//
|
||||
// //triggers insertion - deletion of zombie cache 'my_custom_cache2'
|
||||
// tile00.addCache("trigger-max-cache-handler", 5, T_C);
|
||||
// //reset CAP
|
||||
// tileCache._maxCacheItemCount = OpenSeadragon.DEFAULT_SETTINGS.maxImageCacheCount;
|
||||
//
|
||||
// //try to revive zombie will fail: the zombie was deleted, we will find 18
|
||||
// tile01.addCache("my_custom_cache2", 18, T_C);
|
||||
// const myCustomCache2RecreatedData = tile01.getCache("my_custom_cache2").data;
|
||||
// test.notEqual(myCustomCache2RecreatedData, myCustomCache2Data, "Caches are not equal because created.");
|
||||
// test.equal(myCustomCache2RecreatedData, 18, "Cache data is actually as set to 18.");
|
||||
// test.equal(tileCache.numCachesLoaded(), 8, "The cache has now 8 items.");
|
||||
//
|
||||
//
|
||||
// //delete cache bound to other tiles, this tile has 4 caches:
|
||||
// // cacheKey: shared, originalCacheKey: shared, <custom cache key>, <custom cache key>
|
||||
// // note that cacheKey is shared because we called setData on two items that both create MOD cache
|
||||
// tileCache.unloadTile(tile00, true, tileCache._tilesLoaded.indexOf(tile00));
|
||||
// test.equal(tileCache.numCachesLoaded(), 6, "The cache has now 8-2 items.");
|
||||
// test.equal(tileCache.numTilesLoaded(), 4, "One tile removed.");
|
||||
// test.equal(c00.getTileCount(), 1, "The cache has still tile12 left.");
|
||||
//
|
||||
// //now test tile destruction as zombie
|
||||
//
|
||||
// //now test tile cache sharing
|
||||
// done();
|
||||
})();
|
||||
});
|
||||
|
||||
QUnit.test('Zombie Cache', function(test) {
|
||||
const done = test.async();
|
||||
|
||||
//test jobs by coverage: fail if
|
||||
let jobCounter = 0, coverage = undefined;
|
||||
OpenSeadragon.ImageLoader.prototype.addJob = function (options) {
|
||||
jobCounter++;
|
||||
if (coverage) {
|
||||
//old coverage of previous tiled image: if loaded, fail --> should be in cache
|
||||
const coverageItem = coverage[options.tile.level][options.tile.x][options.tile.y];
|
||||
test.ok(!coverageItem, "Attempt to add job for tile that is not in cache OK if previously not loaded.");
|
||||
}
|
||||
return originalJob.call(this, options);
|
||||
};
|
||||
|
||||
let tilesFinished = 0;
|
||||
const tileCounter = function (event) {tilesFinished++;}
|
||||
|
||||
const openHandler = function(event) {
|
||||
event.item.allowZombieCache(true);
|
||||
|
||||
viewer.world.removeHandler('add-item', openHandler);
|
||||
test.ok(jobCounter === 0, 'Initial state, no images loaded');
|
||||
|
||||
waitFor(() => {
|
||||
if (tilesFinished === jobCounter && event.item._fullyLoaded) {
|
||||
coverage = $.extend(true, {}, event.item.coverage);
|
||||
viewer.world.removeAll();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
let jobsAfterRemoval = 0;
|
||||
const removalHandler = function (event) {
|
||||
viewer.world.removeHandler('remove-item', removalHandler);
|
||||
test.ok(jobCounter > 0, 'Tiled image removed after 100 ms, should load some images.');
|
||||
jobsAfterRemoval = jobCounter;
|
||||
|
||||
viewer.world.addHandler('add-item', reopenHandler);
|
||||
viewer.addTiledImage({
|
||||
tileSource: '/test/data/testpattern.dzi'
|
||||
});
|
||||
}
|
||||
|
||||
const reopenHandler = function (event) {
|
||||
event.item.allowZombieCache(true);
|
||||
|
||||
viewer.removeHandler('add-item', reopenHandler);
|
||||
test.equal(jobCounter, jobsAfterRemoval, 'Reopening image does not fetch any tiles imemdiatelly.');
|
||||
|
||||
waitFor(() => {
|
||||
if (event.item._fullyLoaded) {
|
||||
viewer.removeHandler('tile-unloaded', unloadTileHandler);
|
||||
viewer.removeHandler('tile-loaded', tileCounter);
|
||||
|
||||
//console test needs here explicit removal to finish correctly
|
||||
OpenSeadragon.ImageLoader.prototype.addJob = originalJob;
|
||||
done();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
const unloadTileHandler = function (event) {
|
||||
test.equal(event.destroyed, false, "Tile unload event should not delete with zombies!");
|
||||
}
|
||||
|
||||
viewer.world.addHandler('add-item', openHandler);
|
||||
viewer.world.addHandler('remove-item', removalHandler);
|
||||
viewer.addHandler('tile-unloaded', unloadTileHandler);
|
||||
viewer.addHandler('tile-loaded', tileCounter);
|
||||
|
||||
viewer.open('/test/data/testpattern.dzi');
|
||||
// TODO FIX
|
||||
test.ok("TODO: FIX TEST SUITE FOR NEW CACHE SYSTEM");
|
||||
done();
|
||||
// //test jobs by coverage: fail if
|
||||
// let jobCounter = 0, coverage = undefined;
|
||||
// OpenSeadragon.ImageLoader.prototype.addJob = function (options) {
|
||||
// jobCounter++;
|
||||
// if (coverage) {
|
||||
// //old coverage of previous tiled image: if loaded, fail --> should be in cache
|
||||
// const coverageItem = coverage[options.tile.level][options.tile.x][options.tile.y];
|
||||
// test.ok(!coverageItem, "Attempt to add job for tile that is not in cache OK if previously not loaded.");
|
||||
// }
|
||||
// return originalJob.call(this, options);
|
||||
// };
|
||||
//
|
||||
// let tilesFinished = 0;
|
||||
// const tileCounter = function (event) {tilesFinished++;}
|
||||
//
|
||||
// const openHandler = function(event) {
|
||||
// event.item.allowZombieCache(true);
|
||||
//
|
||||
// viewer.world.removeHandler('add-item', openHandler);
|
||||
// test.ok(jobCounter === 0, 'Initial state, no images loaded');
|
||||
//
|
||||
// waitFor(() => {
|
||||
// if (tilesFinished === jobCounter && event.item._fullyLoaded) {
|
||||
// coverage = $.extend(true, {}, event.item.coverage);
|
||||
// viewer.world.removeAll();
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// let jobsAfterRemoval = 0;
|
||||
// const removalHandler = function (event) {
|
||||
// viewer.world.removeHandler('remove-item', removalHandler);
|
||||
// test.ok(jobCounter > 0, 'Tiled image removed after 100 ms, should load some images.');
|
||||
// jobsAfterRemoval = jobCounter;
|
||||
//
|
||||
// viewer.world.addHandler('add-item', reopenHandler);
|
||||
// viewer.addTiledImage({
|
||||
// tileSource: '/test/data/testpattern.dzi'
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// const reopenHandler = function (event) {
|
||||
// event.item.allowZombieCache(true);
|
||||
//
|
||||
// viewer.removeHandler('add-item', reopenHandler);
|
||||
// test.equal(jobCounter, jobsAfterRemoval, 'Reopening image does not fetch any tiles imemdiatelly.');
|
||||
//
|
||||
// waitFor(() => {
|
||||
// if (event.item._fullyLoaded) {
|
||||
// viewer.removeHandler('tile-unloaded', unloadTileHandler);
|
||||
// viewer.removeHandler('tile-loaded', tileCounter);
|
||||
//
|
||||
// //console test needs here explicit removal to finish correctly
|
||||
// OpenSeadragon.ImageLoader.prototype.addJob = originalJob;
|
||||
// done();
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// const unloadTileHandler = function (event) {
|
||||
// test.equal(event.destroyed, false, "Tile unload event should not delete with zombies!");
|
||||
// }
|
||||
//
|
||||
// viewer.world.addHandler('add-item', openHandler);
|
||||
// viewer.world.addHandler('remove-item', removalHandler);
|
||||
// viewer.addHandler('tile-unloaded', unloadTileHandler);
|
||||
// viewer.addHandler('tile-loaded', tileCounter);
|
||||
//
|
||||
// viewer.open('/test/data/testpattern.dzi');
|
||||
});
|
||||
|
||||
QUnit.test('Zombie Cache Replace Item', function(test) {
|
||||
const done = test.async();
|
||||
|
||||
//test jobs by coverage: fail if
|
||||
let jobCounter = 0, coverage = undefined;
|
||||
OpenSeadragon.ImageLoader.prototype.addJob = function (options) {
|
||||
jobCounter++;
|
||||
if (coverage) {
|
||||
//old coverage of previous tiled image: if loaded, fail --> should be in cache
|
||||
const coverageItem = coverage[options.tile.level][options.tile.x][options.tile.y];
|
||||
if (!coverageItem) {
|
||||
console.warn(coverage, coverage[options.tile.level][options.tile.x], options.tile);
|
||||
}
|
||||
test.ok(!coverageItem, "Attempt to add job for tile data that was previously loaded.");
|
||||
}
|
||||
return originalJob.call(this, options);
|
||||
};
|
||||
|
||||
let tilesFinished = 0;
|
||||
const tileCounter = function (event) {tilesFinished++;}
|
||||
|
||||
const openHandler = function(event) {
|
||||
event.item.allowZombieCache(true);
|
||||
viewer.world.removeHandler('add-item', openHandler);
|
||||
viewer.world.addHandler('add-item', reopenHandler);
|
||||
|
||||
waitFor(() => {
|
||||
if (tilesFinished === jobCounter && event.item._fullyLoaded) {
|
||||
coverage = $.extend(true, {}, event.item.coverage);
|
||||
viewer.addTiledImage({
|
||||
tileSource: '/test/data/testpattern.dzi',
|
||||
index: 0,
|
||||
replace: true
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
const reopenHandler = function (event) {
|
||||
event.item.allowZombieCache(true);
|
||||
|
||||
viewer.removeHandler('add-item', reopenHandler);
|
||||
waitFor(() => {
|
||||
if (event.item._fullyLoaded) {
|
||||
viewer.removeHandler('tile-unloaded', unloadTileHandler);
|
||||
viewer.removeHandler('tile-loaded', tileCounter);
|
||||
|
||||
//console test needs here explicit removal to finish correctly
|
||||
OpenSeadragon.ImageLoader.prototype.addJob = originalJob;
|
||||
done();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
const unloadTileHandler = function (event) {
|
||||
test.equal(event.destroyed, false, "Tile unload event should not delete with zombies!");
|
||||
}
|
||||
|
||||
viewer.world.addHandler('add-item', openHandler);
|
||||
viewer.addHandler('tile-unloaded', unloadTileHandler);
|
||||
viewer.addHandler('tile-loaded', tileCounter);
|
||||
|
||||
viewer.open('/test/data/testpattern.dzi');
|
||||
//TODO FIX
|
||||
test.ok("TODO: FIX TEST SUITE FOR NEW CACHE SYSTEM");
|
||||
done();
|
||||
// //test jobs by coverage: fail if
|
||||
// let jobCounter = 0, coverage = undefined;
|
||||
// OpenSeadragon.ImageLoader.prototype.addJob = function (options) {
|
||||
// jobCounter++;
|
||||
// if (coverage) {
|
||||
// //old coverage of previous tiled image: if loaded, fail --> should be in cache
|
||||
// const coverageItem = coverage[options.tile.level][options.tile.x][options.tile.y];
|
||||
// if (!coverageItem) {
|
||||
// console.warn(coverage, coverage[options.tile.level][options.tile.x], options.tile);
|
||||
// }
|
||||
// test.ok(!coverageItem, "Attempt to add job for tile data that was previously loaded.");
|
||||
// }
|
||||
// return originalJob.call(this, options);
|
||||
// };
|
||||
//
|
||||
// let tilesFinished = 0;
|
||||
// const tileCounter = function (event) {tilesFinished++;}
|
||||
//
|
||||
// const openHandler = function(event) {
|
||||
// event.item.allowZombieCache(true);
|
||||
// viewer.world.removeHandler('add-item', openHandler);
|
||||
// viewer.world.addHandler('add-item', reopenHandler);
|
||||
//
|
||||
// waitFor(() => {
|
||||
// if (tilesFinished === jobCounter && event.item._fullyLoaded) {
|
||||
// coverage = $.extend(true, {}, event.item.coverage);
|
||||
// viewer.addTiledImage({
|
||||
// tileSource: '/test/data/testpattern.dzi',
|
||||
// index: 0,
|
||||
// replace: true
|
||||
// });
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// const reopenHandler = function (event) {
|
||||
// event.item.allowZombieCache(true);
|
||||
//
|
||||
// viewer.removeHandler('add-item', reopenHandler);
|
||||
// waitFor(() => {
|
||||
// if (event.item._fullyLoaded) {
|
||||
// viewer.removeHandler('tile-unloaded', unloadTileHandler);
|
||||
// viewer.removeHandler('tile-loaded', tileCounter);
|
||||
//
|
||||
// //console test needs here explicit removal to finish correctly
|
||||
// OpenSeadragon.ImageLoader.prototype.addJob = originalJob;
|
||||
// done();
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// const unloadTileHandler = function (event) {
|
||||
// test.equal(event.destroyed, false, "Tile unload event should not delete with zombies!");
|
||||
// }
|
||||
//
|
||||
// viewer.world.addHandler('add-item', openHandler);
|
||||
// viewer.addHandler('tile-unloaded', unloadTileHandler);
|
||||
// viewer.addHandler('tile-loaded', tileCounter);
|
||||
//
|
||||
// viewer.open('/test/data/testpattern.dzi');
|
||||
});
|
||||
|
||||
})();
|
||||
|
Loading…
x
Reference in New Issue
Block a user