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