Fix demo further: clear tile processing logics, perform locking on tile level, delete tile if drawn process encountered _destroyed state.

This commit is contained in:
Aiosa 2024-10-23 15:54:31 +02:00
parent 207bc88aab
commit cd60aff5dc
4 changed files with 51 additions and 35 deletions

View File

@ -268,6 +268,18 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
* @private * @private
*/ */
this.processing = false; this.processing = false;
/**
* Remembers last processing time of the tile, 1 if the tile has just been loaded.
* @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 externally.
* @member {Boolean|Number}
* @private
*/
this.transforming = false;
}; };
/** @lends OpenSeadragon.Tile.prototype */ /** @lends OpenSeadragon.Tile.prototype */
@ -603,15 +615,14 @@ $.Tile.prototype = {
tileAllowNotLoaded: _allowTileNotLoaded tileAllowNotLoaded: _allowTileNotLoaded
}); });
this.cacheKey = newCacheKey; this.cacheKey = newCacheKey;
return; } else if (requestedRestore) {
} // If we requested restore, perform now
// If we requested restore, perform now
if (requestedRestore) {
this.tiledImage._tileCache.restoreTilesThatShareOriginalCache( this.tiledImage._tileCache.restoreTilesThatShareOriginalCache(
this, this.getCache(this.originalCacheKey), this.__restoreRequestedFree this, this.getCache(this.originalCacheKey), this.__restoreRequestedFree
); );
} }
// Else no work to be done // If transforming was set, we finished: drawer transform always finishes with updateRenderTarget()
this.transforming = false;
}, },
/** /**

View File

@ -163,15 +163,14 @@
/** /**
* @private * @private
* Access of the data by drawers, synchronous function. * Access of the data by drawers, synchronous function. Should always access a valid main cache, e.g.
* * cache swap should be atomic.
* When drawers access data, they can choose to access this data as internal copy
* *
* @param {OpenSeadragon.DrawerBase} drawer * @param {OpenSeadragon.DrawerBase} drawer
* until 'setData' is called * @param {OpenSeadragon.Tile} tileDrawn
* @returns {any|undefined} desired data if available, undefined if conversion must be done * @returns {any|undefined} desired data if available, undefined if conversion must be done
*/ */
getDataForRendering(drawer) { getDataForRendering(drawer, tileDrawn) {
const supportedTypes = drawer.getSupportedDataFormats(), const supportedTypes = drawer.getSupportedDataFormats(),
keepInternalCopy = drawer.options.usePrivateCache; keepInternalCopy = drawer.options.usePrivateCache;
if (this.loaded && supportedTypes.includes(this.type)) { if (this.loaded && supportedTypes.includes(this.type)) {
@ -180,6 +179,7 @@
if (this._destroyed) { if (this._destroyed) {
$.console.error("Attempt to draw tile with destroyed main cache!"); $.console.error("Attempt to draw tile with destroyed main cache!");
tileDrawn._unload(); // try to restore the state so that the tile is later on fetched again
return undefined; return undefined;
} }

View File

@ -2146,11 +2146,13 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
}).then(_ => { }).then(_ => {
tile.loading = false; tile.loading = false;
tile.loaded = true; tile.loaded = true;
tile.lastProcess = 1;
resolver(tile); resolver(tile);
}); });
} else { } else {
tile.loading = false; tile.loading = false;
tile.loaded = true; tile.loaded = true;
tile.lastProcess = 1;
resolver(tile); resolver(tile);
} }
} }

View File

@ -249,11 +249,12 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
// const promise = this.requestTileInvalidateEvent(priorityTiles, tStamp, restoreTiles); // const promise = this.requestTileInvalidateEvent(priorityTiles, tStamp, restoreTiles);
// return promise.then(() => this.requestTileInvalidateEvent(this.viewer.tileCache.getLoadedTilesFor(null), tStamp, restoreTiles)); // return promise.then(() => this.requestTileInvalidateEvent(this.viewer.tileCache.getLoadedTilesFor(null), tStamp, restoreTiles));
// // Tile-first retrieval fires computation on tiles that share cache, which are filtered out by processing property
//return this.requestTileInvalidateEvent(this.viewer.tileCache.getLoadedTilesFor(null), tStamp, restoreTiles); return this.requestTileInvalidateEvent(this.viewer.tileCache.getLoadedTilesFor(null), tStamp, restoreTiles);
// Try go cache-first order, ensuring all tiles that do have cache entry get processed // Cache-first update tile retrieval is nicer since there might be many tiles sharing
return this.requestTileInvalidateEvent(new Set(Object.values(this.viewer.tileCache._cachesLoaded).map(c => !c._destroyed && c._tiles[0])), tStamp, restoreTiles); // return this.requestTileInvalidateEvent(new Set(Object.values(this.viewer.tileCache._cachesLoaded)
// .map(c => !c._destroyed && c._tiles[0])), tStamp, restoreTiles);
}, },
/** /**
@ -268,23 +269,25 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
* @return {OpenSeadragon.Promise<?>} * @return {OpenSeadragon.Promise<?>}
*/ */
requestTileInvalidateEvent: function(tilesToProcess, tStamp, restoreTiles = true) { requestTileInvalidateEvent: function(tilesToProcess, tStamp, restoreTiles = true) {
const tileList = []; const tileList = [],
markedTiles = [];
for (const tile of tilesToProcess) { for (const tile of tilesToProcess) {
//todo if tiles are processing then it does not update them to the latest stage // We allow re-execution on tiles that are in process but have too low processing timestamp,
// but if we do allow it, then a collision on processing occurs - swap in middle of rendering, we need // which must be solved by ensuring subsequent data calls in the suddenly outdated processing
// to check what is the latest processing stamp and if it is bigger than current process finish we need to abort // pipeline take no effect.
if (!tile || !tile.loaded || tile.processing) { /* || tile.processing*/ // TODO: cross writes on tile when processing cause memory errors - either ensure
// tile makes NOOP for any execution that comes with older stamp, or prevent update routine
// to happen simultanously
if (!tile || !tile.loaded || (tile.processing && tile.processing <= tStamp) || tile.transforming) {
continue; continue;
} }
// TODO: consider locking on the original cache, which should be read only
// or lock the main cache, and compare with tile.processing tstamp
const tileCache = tile.getCache(); const tileCache = tile.getCache();
if (tileCache._updateStamp >= tStamp) {
continue;
}
tileCache._updateStamp = tStamp;
for (let t of tileCache._tiles) { for (let t of tileCache._tiles) {
// Mark all as processing // Mark all related tiles as processing and cache the references to unmark later on
t.processing = tStamp; t.processing = tStamp;
markedTiles.push(t);
} }
tileList.push(tile); tileList.push(tile);
} }
@ -307,23 +310,23 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
tile: tile, tile: tile,
tiledImage: tile.tiledImage, tiledImage: tile.tiledImage,
}).then(() => { }).then(() => {
// asynchronous finisher if (tile.processing === tStamp) {
return tile.updateRenderTargetWithDataTransform(drawerId, supportedFormats, keepInternalCacheCopy); // asynchronous finisher
tile.transforming = tStamp;
return tile.updateRenderTargetWithDataTransform(drawerId, supportedFormats, keepInternalCacheCopy);
}
return null;
}).catch(e => { }).catch(e => {
$.console.error("Update routine error:", e); $.console.error("Update routine error:", e);
}); });
}); });
return $.Promise.all(jobList).then(() => { return $.Promise.all(jobList).then(() => {
for (let tile of tileList) { for (let tile of markedTiles) {
// pass update stamp on the new cache object to avoid needless updates tile.lastProcess = tile.processing;
const newCache = tile.getCache(); tile.processing = false;
if (newCache) { tile.transforming = false;
newCache._updateStamp = tStamp;
tile.processing = false;
}
} }
this.draw(); this.draw();
}); });
}, },