diff --git a/dist/iwmlib.js b/dist/iwmlib.js index ebbf69a..4e0dbb3 100644 --- a/dist/iwmlib.js +++ b/dist/iwmlib.js @@ -2336,6 +2336,8 @@ onMouseWheel(event) { if (this.capture(event) && this.target.onMouseWheel) { this.target.onMouseWheel(event); + } else { + //console.warn('Target has no onMouseWheel callback') } } @@ -2529,6 +2531,8 @@ } if (this.target.onMouseWheel) { this.target.onMouseWheel(event); + } else { + //console.warn('Target has no onMouseWheel callback', this.target) } } } @@ -3232,7 +3236,9 @@ keepOnStage(velocity, collision = 0.5) { let stagePolygon = this.containerPolygon; - if (!stagePolygon) return + // UO: since keepOnStage is called in nextVelocity we need to + // ensure a return value + if (!stagePolygon) return { x: 0, y: 0} let polygon = this.polygon; let bounced = this.bouncing(); if (bounced) { @@ -4200,7 +4206,10 @@ let event = new ResizeEvent(this, { width: w, height: h }); this.onResize(event); } - if (this.resizeButton != null) ; + if (this.resizeButton != null) { + // this.resizeButton.style.width = 50/this.scale+"px" + // this.resizeButton.style.height = 50/this.scale+"px" + } } startResize(e) { diff --git a/dist/iwmlib.pixi.js b/dist/iwmlib.pixi.js index 6840a1b..bf30c2a 100644 --- a/dist/iwmlib.pixi.js +++ b/dist/iwmlib.pixi.js @@ -5500,6 +5500,8 @@ onMouseWheel(event) { if (this.capture(event) && this.target.onMouseWheel) { this.target.onMouseWheel(event); + } else { + //console.warn('Target has no onMouseWheel callback') } } @@ -5693,6 +5695,8 @@ } if (this.target.onMouseWheel) { this.target.onMouseWheel(event); + } else { + //console.warn('Target has no onMouseWheel callback', this.target) } } } @@ -6396,7 +6400,9 @@ keepOnStage(velocity, collision = 0.5) { let stagePolygon = this.containerPolygon; - if (!stagePolygon) return + // UO: since keepOnStage is called in nextVelocity we need to + // ensure a return value + if (!stagePolygon) return { x: 0, y: 0} let polygon = this.polygon; let bounced = this.bouncing(); if (bounced) { @@ -7202,7 +7208,10 @@ let event = new ResizeEvent(this, { width: w, height: h }); this.onResize(event); } - if (this.resizeButton != null) ; + if (this.resizeButton != null) { + // this.resizeButton.style.width = 50/this.scale+"px" + // this.resizeButton.style.height = 50/this.scale+"px" + } } startResize(e) { @@ -7794,8 +7803,11 @@ } } - const deepZoomTileCache = new Map(); - + const registeredTiles = new Map(); + const pendingTiles = new Map(); + /** Implements a baseTexture cache. The last textures are kept for reuse */ + const keepBaseTextures = 0; + const keptBaseTextures = []; /** The current Tile implementation simply uses PIXI.Sprites. * @@ -7809,6 +7821,74 @@ this.register(url); } + /** + * Static method to enable keeping of base textures + * + * @static + * @param {*} value + * @memberof Tile + */ + static enableKeepBaseTextures(value = 1000) { + keepBaseTextures = value; + } + + /** + * Marks the given url as pending. Pending tiles should not be destroyed + * + * @static + * @param {*} url + * @memberof Tile + */ + static schedule(url) { + let count = 0; + if (pendingTiles.has(url)) { + count = pendingTiles.get(url); + } + pendingTiles.set(url, count + 1); + // console.log("Tile.scheduled", url, pendingTiles.size) + } + + /** + * Returns true iff the url is pending + * + * @static + * @returns + * @memberof Tile + */ + static isPending() { + return pendingTiles.has(url) && pendingTiles.get(url) > 0 + } + + /** + * Removes the given url from pending urls. + * + * @static + * @param {*} url + * @memberof Tile + */ + static unschedule(url) { + if (pendingTiles.has(url)) { + let count = pendingTiles.get(url); + if (count > 1) { + pendingTiles.set(url, count - 1); + } + else { + pendingTiles.clear(url); + } + } + // console.log("Tile.unscheduled", url, pendingTiles.size) + } + + /** + * Loads a tile from image using the PIXI.Texture.fromImage method. + * + * @static + * @param {*} imageId + * @param {*} crossorigin + * @param {*} scaleMode + * @returns + * @memberof Tile + */ static fromImage(imageId, crossorigin, scaleMode) { return new Tile(PIXI.Texture.fromImage(imageId, crossorigin, scaleMode), imageId) } @@ -7821,13 +7901,14 @@ * @memberof Tile */ register(url, debug = false) { - if (deepZoomTileCache.has(url)) { - let tiles = deepZoomTileCache.get(url); + Tile.unschedule(url); + if (registeredTiles.has(url)) { + let tiles = registeredTiles.get(url); tiles.add(this); if (debug) console.log("Tile.register", url, tiles.size); } else { - deepZoomTileCache.set(url, new Set([this])); + registeredTiles.set(url, new Set([this])); if (debug) console.log("Tile.register", url, 1); } } @@ -7839,10 +7920,10 @@ * @memberof Tile */ unregister() { - let tiles = deepZoomTileCache.get(this.url); + let tiles = registeredTiles.get(this.url); tiles.delete(this); if (tiles.size == 0) { - deepZoomTileCache.delete(this.url); + registeredTiles.delete(this.url); } return tiles.size } @@ -7853,19 +7934,75 @@ * @param {*} options Part of the PIXI API, but ignored in the implementation * @memberof Tile */ - destroy(options, debug = false) { - if (this.parent != null) ; + destroy(options, debug = true) { let count = this.unregister(); - if (count <= 0) { - let opts = { children: true, texture: true, baseTexture: true }; + + if (keepBaseTextures > 0) { + keptBaseTextures.push({ url: this.url, texture: this.texture.baseTexture}); + + let opts = { children: true, texture: false, baseTexture: false }; + if (debug) console.log("Tile.destroy", registeredTiles.size, opts); super.destroy(opts); - if (debug) console.log("Tile.destroy", deepZoomTileCache.size, opts); + + while(keptBaseTextures.length > keepBaseTextures) { + let {url, texture} = keptBaseTextures.shift(); + let tiles = registeredTiles.get(url); + if (tiles.size > 0 && !Tile.isPending(url)) { + texture.destroy(); + if (debug) console.log("Destroying baseTexture", url); + } + } } else { - let opts = { children: true, texture: false, baseTexture: false }; - if (debug) console.log("Tile.destroy", deepZoomTileCache.size, opts); - super.destroy(opts); + // No longer registered and not pending + if (count <= 0 && !Tile.isPending(url)) { + let opts = { children: true, texture: true, baseTexture: true }; + super.destroy(opts); + if (debug) console.log("Tile.destroy", registeredTiles.size, opts); + } + else { + let opts = { children: true, texture: false, baseTexture: false }; + if (debug) console.log("Tile.destroy", registeredTiles.size, opts); + super.destroy(opts); + } + if (this.parent != null) { + // UO: Emit warning and remove + console.warn("Destroying tile with parent. Hiding instead"); + this.visible = false; + } } + + } + + /** + * Returns an available texture that can be reused + * + * @param {*} url + * @returns + * @memberof Tile + */ + static textureAvailable(url) { + if (registeredTiles.has(url)) { + let tiles = registeredTiles.get(url); + for (let tile of tiles.values()) { + //console.log("Reusing cached texture", tile.parent) + return tile.texture + } + } + return null + } + + static printInfos() { + let references = new Map(); + let multiples = 0; + for (let [url, tiles] of registeredTiles.entries()) { + let count = tiles.size; + references.set(url, count); + if (count > 1) { + multiples += 1; + } + } + console.log({ multiples, references }); } } @@ -7897,6 +8034,8 @@ schedule(url, col, row) { if (this.loaded.has(url)) return false if (this.loading.has(url)) return false + + Tile.schedule(url); this.map.set(url, [col, row]); this.loading.add(url); this.loadQueue.push(url); @@ -7906,6 +8045,7 @@ unschedule(url) { if (this.loaded.has(url)) this.loaded.delete(url); if (this.loading.has(url)) this.loading.delete(url); + Tile.unschedule(url); this.loadQueue = this.loadQueue.filter(item => item != url); } @@ -7931,9 +8071,14 @@ console.warn("Tile already loaded"); tile.unregister(); } - tile = new Tile(texture, url); - this.loaded.set(url, tile); - this.tiles.tileAvailable(tile, col, row, url); + try { + tile = new Tile(texture, url); + this.loaded.set(url, tile); + this.tiles.tileAvailable(tile, col, row, url); + } catch (error) { + console.warn("Tile loading error", error); + } + } } @@ -7960,16 +8105,13 @@ if (this.loaded.has(url)) return false if (this.loading.has(url)) return false - if (deepZoomTileCache.has(url)) { - let tiles = deepZoomTileCache.get(url); - for (let tile of tiles.values()) { - //console.log("Reusing cached texture", tile.parent) - let texture = tile.texture; - this._textureAvailable(url, col, row, texture); - return false - } + Tile.schedule(url); + let reusableTexture = Tile.textureAvailable(url); + if (reusableTexture) { + if (this.debug) console.log('Texture reusable', reusableTexture); + this._textureAvailable(url, col, row, reusableTexture); + return false } - let texture = PIXI.utils.TextureCache[url]; if (texture) { if (this.debug) console.log('Texture already loaded', texture); @@ -8486,18 +8628,8 @@ return n % 2 == 0 } - function printTileCacheInfos() { - let references = new Map(); - let multiples = 0; - for (let [url, tiles] of deepZoomTileCache.entries()) { - let count = tiles.size; - references.set(url, count); - if (count > 1) { - multiples += 1; - } - } - console.log({ multiples, references }); + Tile.printInfos(); } /** * A utility class that holds information typically provided by DZI files, i.e. @@ -8903,7 +9035,7 @@ this.minimumLevel = deepZoomInfo.baseLevel; } this.currentLevel = Math.max(this.minimumLevel, deepZoomInfo.baseLevel); - console.log("autoLoadTiles", this.autoLoadTiles); + //console.log("autoLoadTiles", this.autoLoadTiles) if (this.autoLoadTiles) { this.setupTiles(center); } diff --git a/doc/out/pixi_deepzoom_image.js.html b/doc/out/pixi_deepzoom_image.js.html index 4cea71d..06f8d26 100644 --- a/doc/out/pixi_deepzoom_image.js.html +++ b/doc/out/pixi_deepzoom_image.js.html @@ -1864,7 +1864,7 @@ export class DeepZoomImage extends PIXI.Container { this.minimumLevel = deepZoomInfo.baseLevel } this.currentLevel = Math.max(this.minimumLevel, deepZoomInfo.baseLevel) - console.log("autoLoadTiles", this.autoLoadTiles) + //console.log("autoLoadTiles", this.autoLoadTiles) if (this.autoLoadTiles) { this.setupTiles(center) } diff --git a/lib/pixi/deepzoom/deepzoom.html b/lib/pixi/deepzoom/deepzoom.html index 9a7adf9..786f82c 100644 --- a/lib/pixi/deepzoom/deepzoom.html +++ b/lib/pixi/deepzoom/deepzoom.html @@ -101,7 +101,7 @@ minScale: 0, maxScale: 50, onTransform: event => { - console.log('currentLevel', deepZoomImage1.currentLevel) + //console.log('currentLevel', deepZoomImage1.currentLevel) deepZoomImage1.transformed(event) } }) diff --git a/lib/pixi/deepzoom/image.js b/lib/pixi/deepzoom/image.js index b45cc0b..eb5389e 100755 --- a/lib/pixi/deepzoom/image.js +++ b/lib/pixi/deepzoom/image.js @@ -1,24 +1,14 @@ import { Capabilities } from '../../capabilities.js' import { Points } from '../../utils.js' -import { deepZoomTileCache } from './tile.js' +import Tile from './tile.js' import { Tiles } from './tiles.js' function isEven(n) { return n % 2 == 0 } - function printTileCacheInfos() { - let references = new Map() - let multiples = 0 - for (let [url, tiles] of deepZoomTileCache.entries()) { - let count = tiles.size - references.set(url, count) - if (count > 1) { - multiples += 1 - } - } - console.log({ multiples, references }) + Tile.printInfos() } /** * A utility class that holds information typically provided by DZI files, i.e. @@ -424,7 +414,7 @@ export class DeepZoomImage extends PIXI.Container { this.minimumLevel = deepZoomInfo.baseLevel } this.currentLevel = Math.max(this.minimumLevel, deepZoomInfo.baseLevel) - console.log("autoLoadTiles", this.autoLoadTiles) + //console.log("autoLoadTiles", this.autoLoadTiles) if (this.autoLoadTiles) { this.setupTiles(center) } diff --git a/lib/pixi/deepzoom/loader.js b/lib/pixi/deepzoom/loader.js index 86aed38..6a2d0bc 100644 --- a/lib/pixi/deepzoom/loader.js +++ b/lib/pixi/deepzoom/loader.js @@ -1,4 +1,4 @@ -import { deepZoomTileCache, Tile } from './tile.js' +import Tile from './tile.js' /** * A Tile Loader component that can be plugged into a Tiles Layer. @@ -28,6 +28,8 @@ export class TileLoader { schedule(url, col, row) { if (this.loaded.has(url)) return false if (this.loading.has(url)) return false + + Tile.schedule(url) this.map.set(url, [col, row]) this.loading.add(url) this.loadQueue.push(url) @@ -37,6 +39,7 @@ export class TileLoader { unschedule(url) { if (this.loaded.has(url)) this.loaded.delete(url) if (this.loading.has(url)) this.loading.delete(url) + Tile.unschedule(url) this.loadQueue = this.loadQueue.filter(item => item != url) } @@ -62,9 +65,14 @@ export class TileLoader { console.warn("Tile already loaded") tile.unregister() } - tile = new Tile(texture, url) - this.loaded.set(url, tile) - this.tiles.tileAvailable(tile, col, row, url) + try { + tile = new Tile(texture, url) + this.loaded.set(url, tile) + this.tiles.tileAvailable(tile, col, row, url) + } catch (error) { + console.warn("Tile loading error", error) + } + } } @@ -91,16 +99,13 @@ export class PIXITileLoader extends TileLoader { if (this.loaded.has(url)) return false if (this.loading.has(url)) return false - if (deepZoomTileCache.has(url)) { - let tiles = deepZoomTileCache.get(url) - for (let tile of tiles.values()) { - //console.log("Reusing cached texture", tile.parent) - let texture = tile.texture - this._textureAvailable(url, col, row, texture) - return false - } + Tile.schedule(url) + let reusableTexture = Tile.textureAvailable(url) + if (reusableTexture) { + if (this.debug) console.log('Texture reusable', reusableTexture) + this._textureAvailable(url, col, row, reusableTexture) + return false } - let texture = PIXI.utils.TextureCache[url] if (texture) { if (this.debug) console.log('Texture already loaded', texture) @@ -155,8 +160,8 @@ export class PIXITileLoader extends TileLoader { _onLoaded(loader, resource) { if (this.destroyed) { let texture = resource.texture - let destroyBase = !deepZoomTileCache.has(resource.url) - texture.destroy(destroyBase) + let url = resource.url + Tile.lateTexture(url, texture) console.warn("Received resource after destroy", texture) return } diff --git a/lib/pixi/deepzoom/tile.js b/lib/pixi/deepzoom/tile.js index 7599b96..79c8134 100644 --- a/lib/pixi/deepzoom/tile.js +++ b/lib/pixi/deepzoom/tile.js @@ -1,19 +1,98 @@ -export const deepZoomTileCache = new Map() - +const registeredTiles = new Map() +const pendingTiles = new Map() +/** Implements a baseTexture cache. The last textures are kept for reuse */ +let keepTextures = 0 +const keptTextures = [] /** The current Tile implementation simply uses PIXI.Sprites. * * BTW: PIXI.extras.TilingSprite is not appropriate. It should be used for * repeating patterns. **/ -export class Tile extends PIXI.Sprite { +export default class Tile extends PIXI.Sprite { constructor(texture, url) { super(texture) this.url = url this.register(url) } + /** + * Static method to enable keeping of base textures + * + * @static + * @param {*} value + * @memberof Tile + */ + static enableKeepTextures(value = 1000) { + keepTextures = value + } + + /** + * Marks the given url as pending. Pending tiles should not be destroyed + * + * @static + * @param {*} url + * @memberof Tile + */ + static schedule(url) { + let count = 0 + if (pendingTiles.has(url)) { + count = pendingTiles.get(url) + } + pendingTiles.set(url, count + 1) + // console.log("Tile.scheduled", url, pendingTiles.size) + } + + /** + * Returns true iff the url is pending + * + * @static + * @param {*} url + * @returns + * @memberof Tile + */ + static isPending(url) { + return pendingTiles.has(url) && pendingTiles.get(url) > 0 + } + + static isObsolete(url) { + if (registeredTiles.has(url) && registeredTiles.get(url) > 0) { + return false + } + return true + } + + /** + * Removes the given url from pending urls. + * + * @static + * @param {*} url + * @memberof Tile + */ + static unschedule(url) { + if (pendingTiles.has(url)) { + let count = pendingTiles.get(url) + if (count > 1) { + pendingTiles.set(url, count - 1) + } + else { + pendingTiles.clear(url) + } + } + // console.log("Tile.unscheduled", url, pendingTiles.size) + } + + /** + * Loads a tile from image using the PIXI.Texture.fromImage method. + * + * @static + * @param {*} imageId + * @param {*} crossorigin + * @param {*} scaleMode + * @returns + * @memberof Tile + */ static fromImage(imageId, crossorigin, scaleMode) { return new Tile(PIXI.Texture.fromImage(imageId, crossorigin, scaleMode), imageId) } @@ -26,13 +105,14 @@ export class Tile extends PIXI.Sprite { * @memberof Tile */ register(url, debug = false) { - if (deepZoomTileCache.has(url)) { - let tiles = deepZoomTileCache.get(url) + Tile.unschedule(url) + if (registeredTiles.has(url)) { + let tiles = registeredTiles.get(url) tiles.add(this) if (debug) console.log("Tile.register", url, tiles.size) } else { - deepZoomTileCache.set(url, new Set([this])) + registeredTiles.set(url, new Set([this])) if (debug) console.log("Tile.register", url, 1) } } @@ -44,10 +124,11 @@ export class Tile extends PIXI.Sprite { * @memberof Tile */ unregister() { - let tiles = deepZoomTileCache.get(this.url) + let tiles = registeredTiles.get(this.url) tiles.delete(this) if (tiles.size == 0) { - deepZoomTileCache.delete(this.url) + registeredTiles.delete(this.url) + return 0 } return tiles.size } @@ -58,20 +139,82 @@ export class Tile extends PIXI.Sprite { * @param {*} options Part of the PIXI API, but ignored in the implementation * @memberof Tile */ - destroy(options, debug = false) { - if (this.parent != null) { - - } + destroy(options, debug = true) { let count = this.unregister() - if (count <= 0) { - let opts = { children: true, texture: true, baseTexture: true } + + if (keepTextures > 0) { + keptTextures.push({ url: this.url, texture: this.texture}) + + let opts = { children: true, texture: false, baseTexture: false } + if (debug) console.log("Tile.destroy", registeredTiles.size, opts) super.destroy(opts) - if (debug) console.log("Tile.destroy", deepZoomTileCache.size, opts) + + while(keptTextures.length > keepTextures) { + let {url, texture} = keptTextures.shift() + if (Tile.isObsolete(url)) { + texture.destroy(true) // Destroy base as well + if (debug) console.log("Destroying texture and baseTexture", url) + } + } } else { - let opts = { children: true, texture: false, baseTexture: false } - if (debug) console.log("Tile.destroy", deepZoomTileCache.size, opts) - super.destroy(opts) + // No longer registered and not pending + if (count <= 0 && !Tile.isPending(this.url)) { + let opts = { children: true, texture: true, baseTexture: true } + super.destroy(opts) + if (debug) console.log("Tile.destroy", registeredTiles.size, opts) + } + else { + let opts = { children: true, texture: false, baseTexture: false } + if (debug) console.log("Tile.destroy", registeredTiles.size, opts) + super.destroy(opts) + } + if (this.parent != null) { + // UO: Emit warning and remove + console.warn("Destroying tile with parent. Hiding instead") + this.visible = false + } } } + + /** + * Returns an available texture that can be reused + * + * @param {*} url + * @returns + * @memberof Tile + */ + static textureAvailable(url) { + if (registeredTiles.has(url)) { + let tiles = registeredTiles.get(url) + for (let tile of tiles.values()) { + //console.log("Reusing cached texture", tile.parent) + return tile.texture + } + } + return null + } + + /** + * Texture received too late. We do not need it. + * @param {*} url + * @param {*} texture + */ + static lateTexture(url, texture) { + let destroyBase = !registeredTiles.has(url) + texture.destroy(destroyBase) + } + + static printInfos() { + let references = new Map() + let multiples = 0 + for (let [url, tiles] of registeredTiles.entries()) { + let count = tiles.size + references.set(url, count) + if (count > 1) { + multiples += 1 + } + } + console.log({ multiples, references }) + } } diff --git a/lib/pixi/thumbnails/badge.png b/lib/pixi/thumbnails/badge.png index 56c0d6b..b1764f8 100644 Binary files a/lib/pixi/thumbnails/badge.png and b/lib/pixi/thumbnails/badge.png differ diff --git a/lib/pixi/thumbnails/button.png b/lib/pixi/thumbnails/button.png index f27540e..b557949 100644 Binary files a/lib/pixi/thumbnails/button.png and b/lib/pixi/thumbnails/button.png differ diff --git a/lib/pixi/thumbnails/buttongroup.png b/lib/pixi/thumbnails/buttongroup.png index 318bd83..9f69990 100644 Binary files a/lib/pixi/thumbnails/buttongroup.png and b/lib/pixi/thumbnails/buttongroup.png differ diff --git a/lib/pixi/thumbnails/popup.png b/lib/pixi/thumbnails/popup.png index f0aa561..b23df46 100644 Binary files a/lib/pixi/thumbnails/popup.png and b/lib/pixi/thumbnails/popup.png differ diff --git a/lib/pixi/thumbnails/scatter.png b/lib/pixi/thumbnails/scatter.png index 890e4d6..1a610bf 100644 Binary files a/lib/pixi/thumbnails/scatter.png and b/lib/pixi/thumbnails/scatter.png differ diff --git a/lib/pixi/thumbnails/slider.png b/lib/pixi/thumbnails/slider.png index d24efb3..74ee176 100644 Binary files a/lib/pixi/thumbnails/slider.png and b/lib/pixi/thumbnails/slider.png differ diff --git a/lib/pixi/thumbnails/volatile.png b/lib/pixi/thumbnails/volatile.png index ac1da17..e2ffaf8 100644 Binary files a/lib/pixi/thumbnails/volatile.png and b/lib/pixi/thumbnails/volatile.png differ diff --git a/lib/scatter.js b/lib/scatter.js index 168a6e2..0f2a1cb 100644 --- a/lib/scatter.js +++ b/lib/scatter.js @@ -410,7 +410,9 @@ export class AbstractScatter extends Throwable { keepOnStage(velocity, collision = 0.5) { let stagePolygon = this.containerPolygon - if (!stagePolygon) return + // UO: since keepOnStage is called in nextVelocity we need to + // ensure a return value + if (!stagePolygon) return { x: 0, y: 0} let polygon = this.polygon let bounced = this.bouncing() if (bounced) { diff --git a/package-lock.json b/package-lock.json index 3dbd94e..1281fd0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "iwmlib", - "version": "1.0.8", + "version": "1.0.10", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2770,14 +2770,6 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "^4.1.6" - } - }, "just-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", diff --git a/package.json b/package.json index f7e51f4..70b5381 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iwmlib", - "version": "1.0.8", + "version": "1.0.10", "description": "An Open Source library for multi-touch, WebGL powered applications.", "main": "index.js", "directories": {