Make tile-invalidated event before tile-loaded. Try to fix behavior of maxTilesperFrame

This commit is contained in:
Aiosa 2024-11-05 10:58:41 +01:00
parent 5fdeb382ea
commit 9bfdd55b2e
5 changed files with 97 additions and 132 deletions

View File

@ -305,7 +305,7 @@
* @property {Number} [rotationIncrement=90]
* The number of degrees to rotate right or left when the rotate buttons or keyboard shortcuts are activated.
*
* @property {Number} [maxTilesPerFrame=10]
* @property {Number} [maxTilesPerFrame=1]
* The number of tiles loaded per frame. As the frame rate of the client's machine is usually high (e.g., 50 fps),
* one tile per frame should be a good choice. However, for large screens or lower frame rates, the number of
* loaded tiles per frame can be adjusted here. Reasonable values might be 2 or 3 tiles per frame.
@ -1345,7 +1345,7 @@ function OpenSeadragon( options ){
preserveImageSizeOnResize: false, // requires autoResize=true
minScrollDeltaTime: 50,
rotationIncrement: 90,
maxTilesPerFrame: 10,
maxTilesPerFrame: 1,
//DEFAULT CONTROL SETTINGS
showSequenceControl: true, //SEQUENCE

View File

@ -275,7 +275,8 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
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.
* 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
*/

View File

@ -164,13 +164,17 @@
/**
* @private
* Access of the data by drawers, synchronous function. Should always access a valid main cache, e.g.
* cache swap should be atomic.
* cache swap performed on working cache (consumeCache()) must be synchronous such that cache is always
* ready to render, and swaps atomically between render calls.
*
* @param {OpenSeadragon.DrawerBase} drawer
* @param {OpenSeadragon.Tile} tileDrawn
* @param {OpenSeadragon.DrawerBase} drawer drawer reference which requests the data: the drawer
* defines the supported formats this cache should return **synchronously**
* @param {OpenSeadragon.Tile} tileToDraw reference to the tile that is in the process of drawing and
* for which we request the data; if we attempt to draw such tile while main cache target is destroyed,
* attempt to reset the tile state to force system to re-download it again
* @returns {any|undefined} desired data if available, undefined if conversion must be done
*/
getDataForRendering(drawer, tileDrawn) {
getDataForRendering(drawer, tileToDraw ) {
const supportedTypes = drawer.getSupportedDataFormats(),
keepInternalCopy = drawer.options.usePrivateCache;
if (this.loaded && supportedTypes.includes(this.type)) {
@ -179,7 +183,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
tileToDraw._unload(); // try to restore the state so that the tile is later on fetched again
return undefined;
}

View File

@ -190,6 +190,7 @@ $.TiledImage = function( options ) {
compositeOperation: $.DEFAULT_SETTINGS.compositeOperation,
subPixelRoundingForTransparency: $.DEFAULT_SETTINGS.subPixelRoundingForTransparency,
maxTilesPerFrame: $.DEFAULT_SETTINGS.maxTilesPerFrame,
_currentMaxTilesPerFrame: (options.maxTilesPerFrame || $.DEFAULT_SETTINGS.maxTilesPerFrame) * 10
}, options );
this._preload = this.preload;
@ -299,6 +300,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
*/
reset: function() {
this._tileCache.clearTilesFor(this);
this._currentMaxTilesPerFrame = this.maxTilesPerFrame * 10;
this.lastResetTime = $.now();
this._needsDraw = true;
this._fullyLoaded = false;
@ -1817,7 +1819,11 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
this._tilesLoading++;
} else if (!loadingCoverage) {
// add tile to best tiles to load only when not loaded already
best = this._compareTiles( best, tile, this.maxTilesPerFrame );
best = this._compareTiles( best, tile, this._currentMaxTilesPerFrame );
// TODO: test 'Viewer headers can be updated' fail if we start decreasing the number since not enough tiles get invoked
// if (this._currentMaxTilesPerFrame > this.maxTilesPerFrame) {
// this._currentMaxTilesPerFrame = Math.max(Math.ceil(this.maxTilesPerFrame / 2), this.maxTilesPerFrame);
// }
}
return {
@ -2129,55 +2135,10 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
tile.hasTransparency = tile.hasTransparency || _this.source.hasTransparency(
undefined, tile.getUrl(), tile.ajaxHeaders, tile.postData
);
// tile.updateRenderTarget(true);
// //make sure cache data is ready for drawing, if not, request the desired format
// const cache = tile.getCache(),
// requiredTypes = _this._drawer.getSupportedDataFormats();
// if (!cache) {
// $.console.warn("Tile %s not cached or not loaded at the end of tile-loaded event: tile will not be drawn - it has no data!", tile);
// resolver();
// } else if (!requiredTypes.includes(cache.type)) {
// //initiate conversion as soon as possible if incompatible with the drawer
// //either the cache is a new item in the system (do process), or the cache inherits data from elsewhere (no-op),
// // or the cache was processed in this call
// tile.transforming = now; // block any updates on the tile
// cache.prepareForRendering(_this._drawer.getId(), requiredTypes, _this._drawer.options.usePrivateCache).then(cacheRef => {
// if (!cacheRef) {
// return cache.transformTo(requiredTypes);
// }
// if (tile.processing === now) {
// tile.updateRenderTarget();
// }
// return cacheRef;
// }).then(resolver);
// } else {
// resolver();
// }
// TODO consider first running this event before we call tile-loaded...
if (!tileCacheCreated) {
// Tile-loaded not called on each tile, but only on tiles with new data! Verify we share the main cache
const origCache = tile.getCache(tile.originalCacheKey);
for (let t of origCache._tiles) {
// if there exists a tile that has different main cache, inherit it as a main cache
if (t.cacheKey !== tile.cacheKey) {
// 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);
break;
}
}
resolver();
return;
}
// In case we did not succeed in tile restoration, request invalidation todo what about catch
const updatePromise = _this.viewer.world.requestTileInvalidateEvent([tile], now, false, true);
updatePromise.then(resolver);
tile.loading = false;
tile.loaded = true;
_this.redraw();
resolver(tile);
}
function getCompletionCallback() {
@ -2189,80 +2150,82 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
return completionCallback;
}
const fallbackCompletion = getCompletionCallback();
function markTileAsReady() {
tile.lastProcess = false;
tile.processing = false;
tile.transforming = false;
// if (!tileCacheCreated) {
// // Tile-loaded not called on each tile, but only on tiles with new data! Verify we share the main cache
// const origCache = tile.getCache(tile.originalCacheKey);
// if (!origCache.__invStamp) {
// for (let t of origCache._tiles) {
// if (t.cacheKey !== tile.cacheKey) {
// const targetMainCache = t.getCache();
// tile.addCache(t.cacheKey, targetMainCache.data, targetMainCache.type, true, false);
// fallbackCompletion();
// return;
// }
// }
// }
// // else todo: what if we somehow managed to finish before this tile gets attached? probably impossible if the tile is joined by original cache...
// }
const fallbackCompletion = getCompletionCallback();
// // TODO ENSURE ONLY THESE TWO EVENTS CAN CALL TILE UPDATES
// // prepare for the fact that tile routine can be called here too
// tile.lastProcess = false;
// tile.processing = now;
// tile.transforming = false;
/**
* Triggered when a tile has just been loaded in memory. That means that the
* image has been downloaded and can be modified before being drawn to the canvas.
* This event is _awaiting_, it supports asynchronous functions or functions that return a promise.
*
* @event tile-loaded
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {Image|*} image - The image (data) of the tile. Deprecated.
* @property {*} data image data, the data sent to ImageJob.prototype.finish(),
* by default an Image object. Deprecated
* @property {String} dataType type of the data
* @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile.
* @property {OpenSeadragon.Tile} tile - The tile which has been loaded.
* @property {XMLHttpRequest} tileRequest - The AJAX request that loaded this tile (if applicable).
* @property {OpenSeadragon.Promise} - Promise resolved when the tile gets fully loaded.
* NOTE: do no await the promise in the handler: you will create a deadlock!
* @property {function} getCompletionCallback - deprecated
*/
_this.viewer.raiseEventAwaiting("tile-loaded", {
tile: tile,
tiledImage: _this,
tileRequest: tileRequest,
promise: new $.Promise(resolve => {
resolver = resolve;
}),
get image() {
$.console.error("[tile-loaded] event 'image' has been deprecated. Use 'tile.getData()' instead.");
return data;
},
get data() {
$.console.error("[tile-loaded] event 'data' has been deprecated. Use 'tile.getData()' 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");
return getCompletionCallback();
},
}).catch(() => {
$.console.error("[tile-loaded] event finished with failure: there might be a problem with a plugin you are using.");
}).then(fallbackCompletion);
}
/**
* Triggered when a tile has just been loaded in memory. That means that the
* image has been downloaded and can be modified before being drawn to the canvas.
* This event is _awaiting_, it supports asynchronous functions or functions that return a promise.
*
* @event tile-loaded
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {Image|*} image - The image (data) of the tile. Deprecated.
* @property {*} data image data, the data sent to ImageJob.prototype.finish(),
* by default an Image object. Deprecated
* @property {String} dataType type of the data
* @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile.
* @property {OpenSeadragon.Tile} tile - The tile which has been loaded.
* @property {XMLHttpRequest} tileRequest - The AJAX request that loaded this tile (if applicable).
* @property {OpenSeadragon.Promise} - Promise resolved when the tile gets fully loaded.
* NOTE: do no await the promise in the handler: you will create a deadlock!
* @property {function} getCompletionCallback - deprecated
*/
this.viewer.raiseEventAwaiting("tile-loaded", {
tile: tile,
tiledImage: this,
tileRequest: tileRequest,
promise: new $.Promise(resolve => {
resolver = () => {
tile.loading = false;
tile.loaded = true;
tile.lastProcess = false;
tile.processing = false;
tile.transforming = false;
this.redraw();
resolve(tile);
};
}),
get image() {
$.console.error("[tile-loaded] event 'image' has been deprecated. Use 'tile.getData()' instead.");
return data;
},
get data() {
$.console.error("[tile-loaded] event 'data' has been deprecated. Use 'tile.getData()' 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");
return getCompletionCallback();
},
}).catch(() => {
$.console.error("[tile-loaded] event finished with failure: there might be a problem with a plugin you are using.");
}).then(fallbackCompletion);
if (tileCacheCreated) {
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
const origCache = tile.getCache(tile.originalCacheKey);
for (let t of origCache._tiles) {
// if there exists a tile that has different main cache, inherit it as a main cache
if (t.cacheKey !== tile.cacheKey) {
// 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);
break;
}
}
markTileAsReady();
}
},

View File

@ -75,9 +75,6 @@
if (self.filterIncrement !== currentIncrement) {
break;
}
if (contextCopy.canvas.width === 0) {
debugger;
}
await processors[i](contextCopy);
}