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
*/
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 */
@ -603,15 +615,14 @@ $.Tile.prototype = {
tileAllowNotLoaded: _allowTileNotLoaded
});
this.cacheKey = newCacheKey;
return;
}
// If we requested restore, perform now
if (requestedRestore) {
} else if (requestedRestore) {
// If we requested restore, perform now
this.tiledImage._tileCache.restoreTilesThatShareOriginalCache(
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
* Access of the data by drawers, synchronous function.
*
* When drawers access data, they can choose to access this data as internal copy
* Access of the data by drawers, synchronous function. Should always access a valid main cache, e.g.
* cache swap should be atomic.
*
* @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
*/
getDataForRendering(drawer) {
getDataForRendering(drawer, tileDrawn) {
const supportedTypes = drawer.getSupportedDataFormats(),
keepInternalCopy = drawer.options.usePrivateCache;
if (this.loaded && supportedTypes.includes(this.type)) {
@ -180,6 +179,7 @@
if (this._destroyed) {
$.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;
}

View File

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

View File

@ -249,11 +249,12 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
// const promise = this.requestTileInvalidateEvent(priorityTiles, tStamp, restoreTiles);
// return promise.then(() => this.requestTileInvalidateEvent(this.viewer.tileCache.getLoadedTilesFor(null), tStamp, restoreTiles));
//
//return 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);
// Try go cache-first order, ensuring all tiles that do have cache entry get processed
return this.requestTileInvalidateEvent(new Set(Object.values(this.viewer.tileCache._cachesLoaded).map(c => !c._destroyed && c._tiles[0])), tStamp, restoreTiles);
// 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);
},
/**
@ -268,23 +269,25 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
* @return {OpenSeadragon.Promise<?>}
*/
requestTileInvalidateEvent: function(tilesToProcess, tStamp, restoreTiles = true) {
const tileList = [];
const tileList = [],
markedTiles = [];
for (const tile of tilesToProcess) {
//todo if tiles are processing then it does not update them to the latest stage
// but if we do allow it, then a collision on processing occurs - swap in middle of rendering, we need
// to check what is the latest processing stamp and if it is bigger than current process finish we need to abort
if (!tile || !tile.loaded || tile.processing) { /* || tile.processing*/
// 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.
// 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;
}
// 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();
if (tileCache._updateStamp >= tStamp) {
continue;
}
tileCache._updateStamp = tStamp;
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;
markedTiles.push(t);
}
tileList.push(tile);
}
@ -307,23 +310,23 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
tile: tile,
tiledImage: tile.tiledImage,
}).then(() => {
// asynchronous finisher
return tile.updateRenderTargetWithDataTransform(drawerId, supportedFormats, keepInternalCacheCopy);
if (tile.processing === tStamp) {
// asynchronous finisher
tile.transforming = tStamp;
return tile.updateRenderTargetWithDataTransform(drawerId, supportedFormats, keepInternalCacheCopy);
}
return null;
}).catch(e => {
$.console.error("Update routine error:", e);
});
});
return $.Promise.all(jobList).then(() => {
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;
tile.processing = false;
}
for (let tile of markedTiles) {
tile.lastProcess = tile.processing;
tile.processing = false;
tile.transforming = false;
}
this.draw();
});
},