From f127014f0f76298c0211808728aa34f8522c45a0 Mon Sep 17 00:00:00 2001 From: Aiosa <469130@mail.muni.cz> Date: Fri, 1 Nov 2024 21:43:12 +0100 Subject: [PATCH] Design of separated events: drop update data support for tile-loaded, use only invalidated event. --- src/eventsource.js | 12 +- src/tile.js | 21 ++- src/tilecache.js | 50 ++----- src/tiledimage.js | 125 ++++++++++++------ src/world.js | 32 +++-- test/demo/filtering-plugin/plugin.js | 10 +- ...plugin-data-modification-interaction.html} | 8 +- test/modules/data-manipulation.js | 41 +++--- 8 files changed, 169 insertions(+), 130 deletions(-) rename test/demo/{modify-data-with-events.html => plugin-data-modification-interaction.html} (91%) diff --git a/src/eventsource.js b/src/eventsource.js index 058a4220..800621b9 100644 --- a/src/eventsource.js +++ b/src/eventsource.js @@ -199,8 +199,9 @@ $.EventSource.prototype = { * calling the handler for each and awaiting async ones. * @function * @param {String} eventName - Name of event to get handlers for. + * @param {any} bindTarget - Bound target to return with the promise on finish */ - getAwaitingHandler: function ( eventName) { + getAwaitingHandler: function ( eventName, bindTarget ) { let events = this.events[ eventName ]; if ( !events || !events.length ) { return null; @@ -217,7 +218,7 @@ $.EventSource.prototype = { const length = events.length; function loop(index) { if ( index >= length || !events[ index ] ) { - resolve("Resolved!"); + resolve(bindTarget); return null; } args.eventSource = source; @@ -259,17 +260,18 @@ $.EventSource.prototype = { * This events awaits every asynchronous or promise-returning function. * @param {String} eventName - Name of event to register. * @param {Object} eventArgs - Event-specific data. + * @param {?} [bindTarget = null] - Promise-resolved value on the event finish * @return {OpenSeadragon.Promise|undefined} - Promise resolved upon the event completion. */ - raiseEventAwaiting: function ( eventName, eventArgs ) { + raiseEventAwaiting: function ( eventName, eventArgs, bindTarget = null ) { //uncomment if you want to get a log of all events //$.console.log( "Awaiting event fired:", eventName ); - const awaitingHandler = this.getAwaitingHandler(eventName); + const awaitingHandler = this.getAwaitingHandler(eventName, bindTarget); if (awaitingHandler) { return awaitingHandler(this, eventArgs || {}); } - return $.Promise.resolve("No handler for this event registered."); + return $.Promise.resolve(bindTarget); }, /** diff --git a/src/tile.js b/src/tile.js index 4e1350cb..a938e227 100644 --- a/src/tile.js +++ b/src/tile.js @@ -567,25 +567,34 @@ $.Tile.prototype = { * @private * @return {OpenSeadragon.Promise} */ - updateRenderTargetWithDataTransform: function (drawerId, supportedFormats, usePrivateCache) { + updateRenderTargetWithDataTransform: function (drawerId, supportedFormats, usePrivateCache, processTimestamp) { // Now, if working cache exists, we set main cache to the working cache --> prepare - const cache = this.getCache(this._wcKey); + let cache = this.getCache(this._wcKey); if (cache) { - return cache.prepareForRendering(drawerId, supportedFormats, usePrivateCache, this.processing); + 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) { - const cache = this.getCache(this.originalCacheKey); + cache = this.getCache(this.originalCacheKey); this.tiledImage._tileCache.restoreTilesThatShareOriginalCache( this, cache, this.__restoreRequestedFree ); this.__restore = false; - return cache.prepareForRendering(drawerId, supportedFormats, usePrivateCache, this.processing); + return cache.prepareForRendering(drawerId, supportedFormats, usePrivateCache).then((c) => { + if (c && processTimestamp && this.processing === processTimestamp) { + this.updateRenderTarget(); + } + }); } - return $.Promise.resolve(); + cache = this.getCache(); + return cache.prepareForRendering(drawerId, supportedFormats, usePrivateCache); }, /** diff --git a/src/tilecache.js b/src/tilecache.js index 2b36735f..c2c3ed0c 100644 --- a/src/tilecache.js +++ b/src/tilecache.js @@ -223,32 +223,18 @@ * @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} + + * @return {OpenSeadragon.Promise | null} + * reference to the cache processed for drawer rendering requirements, or null on error */ - prepareForRendering(drawerId, supportedTypes, keepInternalCopy = true, _shareTileUpdateStamp = null) { - - const fin = () => { - // Locked update of render target to the tile that initiated processing - if (_shareTileUpdateStamp) { - for (let tile of this._tiles) { - if (tile.processing === _shareTileUpdateStamp) { - tile.updateRenderTarget(); - } - } - } - return this; - }; - + prepareForRendering(drawerId, supportedTypes, keepInternalCopy = true) { // if not internal copy and we have no data, or we are ready to render, exit if (!this.loaded || supportedTypes.includes(this.type)) { - fin(); return $.Promise.resolve(this); } if (!keepInternalCopy) { - return this.transformTo(supportedTypes).then(fin); + return this.transformTo(supportedTypes); } // we can get here only if we want to render incompatible type @@ -260,11 +246,10 @@ internalCache = internalCache[drawerId]; if (internalCache) { // already done - fin(); return $.Promise.resolve(this); - } else { - internalCache = this[DRAWER_INTERNAL_CACHE][drawerId] = new $.SimpleCacheRecord(); } + + internalCache = this[DRAWER_INTERNAL_CACHE][drawerId] = new $.SimpleCacheRecord(); const conversionPath = $.convertor.getConversionPath(this.type, supportedTypes); if (!conversionPath) { $.console.error(`[getDataForRendering] Conversion ${this.type} ---> ${supportedTypes} cannot be done!`); @@ -273,8 +258,8 @@ 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); // synchronous, SimpleCacheRecord - return fin(); + internalCache.setDataAs(data, selectedFormat); // synchronous, SimpleCacheRecord call + return internalCache; }); } @@ -842,10 +827,6 @@ } cacheRecord.addTile(theTile, options.data, options.dataType); - if (cacheKey === theTile.cacheKey) { - theTile.tiledImage._needsDraw = true; - } - this._freeOldRecordRoutine(theTile, options.cutoff || 0); return cacheRecord; } @@ -949,10 +930,7 @@ // 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) { - if (tile.loaded || tile.loading) { - this.unloadCacheForTile(tile, options.consumerKey, true); - } - + this.unloadCacheForTile(tile, options.consumerKey, true, false); } } if (this._cachesLoaded[options.consumerKey]) { @@ -968,7 +946,7 @@ // 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.loading)) { + if (tile !== options.tile) { tile.addCache(options.consumerKey, resultCache.data, resultCache.type, true, false); } } @@ -985,7 +963,7 @@ */ restoreTilesThatShareOriginalCache(tile, originalCache, freeIfUnused) { for (let t of originalCache._tiles) { - this.unloadCacheForTile(t, t.cacheKey, freeIfUnused); + this.unloadCacheForTile(t, t.cacheKey, freeIfUnused, false); delete t._caches[t.cacheKey]; t.cacheKey = t.originalCacheKey; } @@ -1016,7 +994,7 @@ if ( prevTile.level <= cutoff || prevTile.beingDrawn || prevTile.loading || - prevTile.processing ) { + prevTile.processing ) { //todo exempt from deletion, or block this routine on data updates continue; } if ( !worstTile ) { @@ -1171,7 +1149,7 @@ for (let key in tile._caches) { //we are 'ok' to remove tile caches here since we later call destroy on tile, otherwise //tile has count of its cache size --> would be inconsistent - this.unloadCacheForTile(tile, key, destroy); + this.unloadCacheForTile(tile, key, destroy, false); } //delete also the tile record if (deleteAtIndex !== undefined) { diff --git a/src/tiledimage.js b/src/tiledimage.js index 798fae43..bfd9c23d 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -2116,45 +2116,68 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag increment = 0, eventFinished = false; const _this = this, - finishPromise = new $.Promise(r => { - resolver = r; - }); + now = $.now(); function completionCallback() { increment--; if (increment > 0) { return; } + eventFinished = true; + //do not override true if set (false is default) 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(tile.cacheKey), - 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(tile); - } else if (!requiredTypes.includes(cache.type)) { - //initiate conversion as soon as possible if incompatible with the drawer - cache.prepareForRendering(_this._drawer.getId(), requiredTypes, _this._drawer.options.usePrivateCache).then(cacheRef => { - if (!cacheRef) { - return cache.transformTo(requiredTypes); + // 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; } - return cacheRef; - }).then(_ => { - tile.loading = false; - tile.loaded = true; - tile.lastProcess = 1; - resolver(tile); - }); - } else { - tile.loading = false; - tile.loaded = true; - tile.lastProcess = 1; - resolver(tile); + } + + 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); } function getCompletionCallback() { @@ -2168,18 +2191,27 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag const fallbackCompletion = getCompletionCallback(); - 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 (!t.processing && t.cacheKey !== tile.cacheKey) { - const targetMainCache = t.getCache(); - tile.addCache(t.cacheKey, targetMainCache.data, targetMainCache.type, true, false); - fallbackCompletion(); - return; - } - } - } + // 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... + // } + + // // 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 @@ -2197,13 +2229,24 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @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: finishPromise, + 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; @@ -2219,8 +2262,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag }, }).catch(() => { $.console.error("[tile-loaded] event finished with failure: there might be a problem with a plugin you are using."); - }).then(() => { - eventFinished = true; }).then(fallbackCompletion); }, diff --git a/src/world.js b/src/world.js index c865fcf6..bfa4901a 100644 --- a/src/world.js +++ b/src/world.js @@ -265,30 +265,34 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W * @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 + * @param {Boolean} [_allowTileUnloaded=false] internal flag for calling on tiles that come new to the system * @fires OpenSeadragon.Viewer.event:tile-invalidated * @return {OpenSeadragon.Promise} */ - requestTileInvalidateEvent: function(tilesToProcess, tStamp, restoreTiles = true) { + requestTileInvalidateEvent: function(tilesToProcess, tStamp, restoreTiles = true, _allowTileUnloaded = false) { const tileList = [], markedTiles = []; for (const tile of tilesToProcess) { // 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) { + if (!tile || (!_allowTileUnloaded && !tile.loaded) || 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(); + const tileCache = tile.getCache(tile.originalCacheKey); + if (tileCache.__invStamp && tileCache.__invStamp >= tStamp) { + continue; + } + for (let t of tileCache._tiles) { - // Mark all related tiles as processing and cache the references to unmark later on + // Mark all related tiles as processing and cache the references to unmark later on, + // last processing is set to old processing (null if finished) + t.lastProcess = t.processing; t.processing = tStamp; markedTiles.push(t); } + + tileCache.__invStamp = tStamp; tileList.push(tile); } @@ -309,11 +313,13 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W return eventTarget.raiseEventAwaiting('tile-invalidated', { tile: tile, tiledImage: tile.tiledImage, - }).then(() => { - if (tile.processing === tStamp) { + }, tile.getCache(tile.originalCacheKey)).then(cacheKey => { + if (cacheKey.__invStamp === tStamp) { // asynchronous finisher tile.transforming = tStamp; - return tile.updateRenderTargetWithDataTransform(drawerId, supportedFormats, keepInternalCacheCopy); + return tile.updateRenderTargetWithDataTransform(drawerId, supportedFormats, keepInternalCacheCopy, tStamp).then(() => { + cacheKey.__invStamp = null; + }); } return null; }).catch(e => { @@ -323,7 +329,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W return $.Promise.all(jobList).then(() => { for (let tile of markedTiles) { - tile.lastProcess = tile.processing; + tile.lastProcess = false; tile.processing = false; tile.transforming = false; } diff --git a/test/demo/filtering-plugin/plugin.js b/test/demo/filtering-plugin/plugin.js index 47ea8b2b..6caa1233 100644 --- a/test/demo/filtering-plugin/plugin.js +++ b/test/demo/filtering-plugin/plugin.js @@ -47,8 +47,6 @@ } const self = this; this.viewer = options.viewer; - - this.viewer.addHandler('tile-loaded', applyFilters); this.viewer.addHandler('tile-invalidated', applyFilters); // filterIncrement allows to determine whether a tile contains the @@ -67,11 +65,19 @@ } const contextCopy = await tile.getData('context2d'); + + if (contextCopy.canvas.width === 0) { + debugger; + } + const currentIncrement = self.filterIncrement; for (let i = 0; i < processors.length; i++) { if (self.filterIncrement !== currentIncrement) { break; } + if (contextCopy.canvas.width === 0) { + debugger; + } await processors[i](contextCopy); } diff --git a/test/demo/modify-data-with-events.html b/test/demo/plugin-data-modification-interaction.html similarity index 91% rename from test/demo/modify-data-with-events.html rename to test/demo/plugin-data-modification-interaction.html index 410f036b..de3941e3 100644 --- a/test/demo/modify-data-with-events.html +++ b/test/demo/plugin-data-modification-interaction.html @@ -54,18 +54,14 @@ } if (_pA) { - viewer.removeHandler('tile-loaded', _pA); viewer.removeHandler('tile-invalidated', _pA); } if (_pB) { - viewer.removeHandler('tile-loaded', _pB); viewer.removeHandler('tile-invalidated', _pB); } _pA = window.pluginA; _pB = window.pluginB; - viewer.addHandler('tile-loaded', _pA, null, window.orderPluginA || 0); - viewer.addHandler('tile-invalidated', _pA, null), window.orderPluginA || 0; - viewer.addHandler('tile-loaded', _pB, null, window.orderPluginB || 0); + viewer.addHandler('tile-invalidated', _pA, null, window.orderPluginA || 0); viewer.addHandler('tile-invalidated', _pB, null, window.orderPluginB || 0); viewer.requestInvalidate(); } catch (error) { @@ -119,9 +115,7 @@ window.orderPluginB = 0; diff --git a/test/modules/data-manipulation.js b/test/modules/data-manipulation.js index 3b10e504..4eb5dc21 100644 --- a/test/modules/data-manipulation.js +++ b/test/modules/data-manipulation.js @@ -17,8 +17,10 @@ } }); + const PROMISE_REF_KEY = Symbol("_private_test_ref"); + OpenSeadragon.getBuiltInDrawersForTest().forEach(testDrawer); - // If yuu want to debug a specific drawer, use instead: + // If you want to debug a specific drawer, use instead: // ['webgl'].forEach(testDrawer); function getPluginCode(overlayColor = "rgba(0,0,255,0.5)") { @@ -74,21 +76,31 @@ context.finish(ctx, null, "context2d"); } }); + + // Get promise reference to wait for tile ready + viewer.addHandler('tile-loaded', e => { + e.tile[PROMISE_REF_KEY] = e.promise; + }); } const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) // we test middle of the canvas, so that we can test both tiles or the output canvas of canvas drawer :) - async function readTileData() { + async function readTileData(tileRef = null) { + // Get some time for viewer to load data + await sleep(50); + // make sure at least one tile loaded + const tile = tileRef || viewer.world.getItemAt(0).getTilesToDraw()[0]; + await tile[PROMISE_REF_KEY]; + // Get some time for viewer to load data + await sleep(50); + if (type === "canvas") { //test with the underlying canvas instead const canvas = viewer.drawer.canvas; return viewer.drawer.canvas.getContext("2d").getImageData(canvas.width/2, canvas.height/2, 1, 1); } - await sleep(200); - //else incompatible drawer for data getting - const tile = viewer.world.getItemAt(0).getTilesToDraw()[0]; const cache = tile.tile.getCache(); if (!cache || !cache.loaded) return null; @@ -103,12 +115,8 @@ const fnA = getPluginCode("rgba(0,0,255,1)"); const fnB = getPluginCode("rgba(255,0,0,1)"); - const crashTest = () => assert.ok(false, "Tile Invalidated event should not be called"); - - viewer.addHandler('tile-loaded', fnA); - viewer.addHandler('tile-invalidated', crashTest); - viewer.addHandler('tile-loaded', fnB); - viewer.addHandler('tile-invalidated', crashTest); + viewer.addHandler('tile-invalidated', fnA); + viewer.addHandler('tile-invalidated', fnB); viewer.addHandler('open', async () => { await viewer.waitForFinishedJobsForTest(); @@ -120,6 +128,7 @@ // Thorough testing of the cache state for (let tile of viewer.tileCache._tilesLoaded) { + await tile[PROMISE_REF_KEY]; // to be sure all tiles has finished before checking const caches = Object.entries(tile._caches); assert.equal(caches.length, 2, `Tile ${getTileDescription(tile)} has only two caches - main & original`); @@ -142,9 +151,7 @@ const fnA = getPluginCode("rgba(0,0,255,1)"); const fnB = getPluginCode("rgba(255,0,0,1)"); - viewer.addHandler('tile-loaded', fnA); viewer.addHandler('tile-invalidated', fnA); - viewer.addHandler('tile-loaded', fnB, null, 1); viewer.addHandler('tile-invalidated', fnB, null, 1); // const promise = viewer.requestInvalidate(); @@ -158,7 +165,6 @@ assert.equal(data.data[3], 255); // Test swap - viewer.addHandler('tile-loaded', fnB); viewer.addHandler('tile-invalidated', fnB); await viewer.requestInvalidate(); @@ -170,7 +176,6 @@ assert.equal(data.data[3], 255); // Now B gets applied last! Red - viewer.addHandler('tile-loaded', fnB, null, -1); viewer.addHandler('tile-invalidated', fnB, null, -1); await viewer.requestInvalidate(); // no change @@ -182,6 +187,7 @@ // Thorough testing of the cache state for (let tile of viewer.tileCache._tilesLoaded) { + await tile[PROMISE_REF_KEY]; // to be sure all tiles has finished before checking const caches = Object.entries(tile._caches); assert.equal(caches.length, 2, `Tile ${getTileDescription(tile)} has only two caches - main & original`); @@ -204,9 +210,7 @@ const fnA = getPluginCode("rgba(0,255,0,1)"); const fnB = getResetTileDataCode(); - viewer.addHandler('tile-loaded', fnA); viewer.addHandler('tile-invalidated', fnA); - viewer.addHandler('tile-loaded', fnB, null, 1); viewer.addHandler('tile-invalidated', fnB, null, 1); // const promise = viewer.requestInvalidate(); @@ -220,7 +224,6 @@ assert.equal(data.data[3], 255); // Test swap - suddenly B applied since it was added later - viewer.addHandler('tile-loaded', fnB); viewer.addHandler('tile-invalidated', fnB); await viewer.requestInvalidate(); data = await readTileData(); @@ -229,7 +232,6 @@ assert.equal(data.data[2], 255); assert.equal(data.data[3], 255); - viewer.addHandler('tile-loaded', fnB, null, -1); viewer.addHandler('tile-invalidated', fnB, null, -1); await viewer.requestInvalidate(); data = await readTileData(); @@ -241,6 +243,7 @@ // Thorough testing of the cache state for (let tile of viewer.tileCache._tilesLoaded) { + await tile[PROMISE_REF_KEY]; // to be sure all tiles has finished before checking const caches = Object.entries(tile._caches); assert.equal(caches.length, 1, `Tile ${getTileDescription(tile)} has only single, original cache`);