Fixed memory problems of deepzoom images.
This commit is contained in:
parent
13ea23186f
commit
81f18ea2e9
@ -98,7 +98,7 @@ export default class PIXIApp extends PIXI.Application {
|
|||||||
roundPixels = true,
|
roundPixels = true,
|
||||||
monkeyPatchMapping = true,
|
monkeyPatchMapping = true,
|
||||||
adaptive = true,
|
adaptive = true,
|
||||||
graphql = false,
|
graphql = false
|
||||||
}) {
|
}) {
|
||||||
const fullScreen = !width || !height
|
const fullScreen = !width || !height
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ export default class PIXIApp extends PIXI.Application {
|
|||||||
resolution,
|
resolution,
|
||||||
autoResize,
|
autoResize,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
forceCanvas,
|
forceCanvas
|
||||||
})
|
})
|
||||||
|
|
||||||
this.width = width
|
this.width = width
|
||||||
@ -137,7 +137,7 @@ export default class PIXIApp extends PIXI.Application {
|
|||||||
this.graphql = graphql
|
this.graphql = graphql
|
||||||
if (fullScreen || autoResize) {
|
if (fullScreen || autoResize) {
|
||||||
console.log('App is in fullScreen mode or autoResize mode')
|
console.log('App is in fullScreen mode or autoResize mode')
|
||||||
const resizeDebounced = debounce((event) => this.resizeApp(event), 50)
|
const resizeDebounced = debounce(event => this.resizeApp(event), 50)
|
||||||
window.addEventListener('resize', resizeDebounced)
|
window.addEventListener('resize', resizeDebounced)
|
||||||
document.body.addEventListener('orientationchange', this.checkOrientation.bind(this))
|
document.body.addEventListener('orientationchange', this.checkOrientation.bind(this))
|
||||||
}
|
}
|
||||||
@ -167,25 +167,25 @@ export default class PIXIApp extends PIXI.Application {
|
|||||||
// GraphQL
|
// GraphQL
|
||||||
if (this.graphql && typeof apollo !== 'undefined') {
|
if (this.graphql && typeof apollo !== 'undefined') {
|
||||||
const networkInterface = apollo.createNetworkInterface({
|
const networkInterface = apollo.createNetworkInterface({
|
||||||
uri: '/graphql',
|
uri: '/graphql'
|
||||||
})
|
})
|
||||||
|
|
||||||
const wsClient = new subscriptions.SubscriptionClient(`wss://${location.hostname}/subscriptions`, {
|
const wsClient = new subscriptions.SubscriptionClient(`wss://${location.hostname}/subscriptions`, {
|
||||||
reconnect: true,
|
reconnect: true,
|
||||||
connectionParams: {},
|
connectionParams: {}
|
||||||
})
|
})
|
||||||
|
|
||||||
const networkInterfaceWithSubscriptions = subscriptions.addGraphQLSubscriptions(networkInterface, wsClient)
|
const networkInterfaceWithSubscriptions = subscriptions.addGraphQLSubscriptions(networkInterface, wsClient)
|
||||||
|
|
||||||
this.apolloClient = new apollo.ApolloClient({
|
this.apolloClient = new apollo.ApolloClient({
|
||||||
networkInterface: networkInterfaceWithSubscriptions,
|
networkInterface: networkInterfaceWithSubscriptions
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// progress
|
// progress
|
||||||
this._progress = new Progress(
|
this._progress = new Progress(
|
||||||
Object.assign({ theme: this.theme }, this.progressOpts, {
|
Object.assign({ theme: this.theme }, this.progressOpts, {
|
||||||
app: this,
|
app: this
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
this._progress.visible = false
|
this._progress.visible = false
|
||||||
@ -214,7 +214,7 @@ export default class PIXIApp extends PIXI.Application {
|
|||||||
if (value != this.orient) {
|
if (value != this.orient) {
|
||||||
setTimeout(
|
setTimeout(
|
||||||
100,
|
100,
|
||||||
function () {
|
function() {
|
||||||
this.orientationChanged(true)
|
this.orientationChanged(true)
|
||||||
}.bind(this)
|
}.bind(this)
|
||||||
)
|
)
|
||||||
@ -469,7 +469,7 @@ export default class PIXIApp extends PIXI.Application {
|
|||||||
loadSprites(resources, loaded = null, { resolutionDependent = true, progress = false } = {}) {
|
loadSprites(resources, loaded = null, { resolutionDependent = true, progress = false } = {}) {
|
||||||
this.loadTextures(
|
this.loadTextures(
|
||||||
resources,
|
resources,
|
||||||
(textures) => {
|
textures => {
|
||||||
let sprites = new Map()
|
let sprites = new Map()
|
||||||
|
|
||||||
for (let [key, texture] of textures) {
|
for (let [key, texture] of textures) {
|
||||||
@ -528,7 +528,7 @@ export default class PIXIApp extends PIXI.Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (progress) {
|
if (progress) {
|
||||||
loader.on('progress', (e) => {
|
loader.on('progress', e => {
|
||||||
this.progress(e.progress)
|
this.progress(e.progress)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -719,7 +719,7 @@ class FpsDisplay extends PIXI.Graphics {
|
|||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fill: '#f6f6f6',
|
fill: '#f6f6f6',
|
||||||
stroke: '#434f4f',
|
stroke: '#434f4f',
|
||||||
strokeThickness: 3,
|
strokeThickness: 3
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
this.text.position.set(6, 6)
|
this.text.position.set(6, 6)
|
||||||
|
@ -155,7 +155,7 @@ export class DeepZoomInfo {
|
|||||||
this.baseURL = this.urlForTile(this.baseLevel, 0, 0, false)
|
this.baseURL = this.urlForTile(this.baseLevel, 0, 0, false)
|
||||||
|
|
||||||
if (loadBaseImage) {
|
if (loadBaseImage) {
|
||||||
this.imageForURL(this.baseURL, (e) => {
|
this.imageForURL(this.baseURL, e => {
|
||||||
this.size = [e.target.naturalWidth, e.target.naturalHeight]
|
this.size = [e.target.naturalWidth, e.target.naturalHeight]
|
||||||
this.baseImage = e.target
|
this.baseImage = e.target
|
||||||
})
|
})
|
||||||
@ -332,7 +332,7 @@ export class DeepZoomImage extends PIXI.Container {
|
|||||||
useWorker = '',
|
useWorker = '',
|
||||||
minimumLevel = 0,
|
minimumLevel = 0,
|
||||||
alpha = 1,
|
alpha = 1,
|
||||||
app = window.app,
|
app = window.app
|
||||||
} = {}
|
} = {}
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
@ -708,7 +708,7 @@ export class DeepZoomImage extends PIXI.Container {
|
|||||||
let changed = { added: [], removed: [] }
|
let changed = { added: [], removed: [] }
|
||||||
let newNeeded = new Map()
|
let newNeeded = new Map()
|
||||||
let { centerCol, centerRow, needed } = this.neededTiles(tiles, level)
|
let { centerCol, centerRow, needed } = this.neededTiles(tiles, level)
|
||||||
needed.forEach((d) => {
|
needed.forEach(d => {
|
||||||
let [url, col, row] = d
|
let [url, col, row] = d
|
||||||
newNeeded.set(url, [col, row])
|
newNeeded.set(url, [col, row])
|
||||||
if (!tiles.requested.has(url)) {
|
if (!tiles.requested.has(url)) {
|
||||||
@ -856,7 +856,7 @@ export class DeepZoomImage extends PIXI.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hideTilesAboveLevel(level) {
|
hideTilesAboveLevel(level) {
|
||||||
Object.keys(this.tileLayers).forEach((key) => {
|
Object.keys(this.tileLayers).forEach(key => {
|
||||||
let tiles = this.tileLayers[key]
|
let tiles = this.tileLayers[key]
|
||||||
if (tiles.level > level) {
|
if (tiles.level > level) {
|
||||||
tiles.visible = false
|
tiles.visible = false
|
||||||
@ -869,7 +869,7 @@ export class DeepZoomImage extends PIXI.Container {
|
|||||||
* @param {number} level - The zoom level of the grid
|
* @param {number} level - The zoom level of the grid
|
||||||
*/
|
*/
|
||||||
destroyTilesAboveLevel(level) {
|
destroyTilesAboveLevel(level) {
|
||||||
Object.keys(this.tileLayers).forEach((key) => {
|
Object.keys(this.tileLayers).forEach(key => {
|
||||||
let tiles = this.tileLayers[key]
|
let tiles = this.tileLayers[key]
|
||||||
if (tiles.level > level && !tiles.keep) {
|
if (tiles.level > level && !tiles.keep) {
|
||||||
for (let url of tiles.available.keys()) {
|
for (let url of tiles.available.keys()) {
|
||||||
@ -882,18 +882,38 @@ export class DeepZoomImage extends PIXI.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroyAllTiles() {
|
destroyAllTiles() {
|
||||||
Object.keys(this.tileLayers).forEach((key) => {
|
Object.keys(this.tileLayers).forEach(key => {
|
||||||
this.destroyTiles(key)
|
this.destroyTiles(key)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Destroys all tiles and laywer above the current level to ensure that the memory can
|
||||||
|
* be reused. This must be called from time to time to free load worker which otherwise would
|
||||||
|
* consume memory and threads permanentely.
|
||||||
|
*/
|
||||||
|
destroyUnusedTilesAndLayers() {
|
||||||
|
this.destroyTilesAboveLevel(this.currentLevel)
|
||||||
|
let candidates = []
|
||||||
|
Object.keys(this.tileLayers).forEach(key => {
|
||||||
|
let tiles = this.tileLayers[key]
|
||||||
|
if (tiles.level > this.currentLevel) {
|
||||||
|
candidates.push(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for (let key of candidates) {
|
||||||
|
let tiles = this.tileLayers[key]
|
||||||
|
tiles.destroy()
|
||||||
|
delete this.tileLayers[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tint tiles in all layers that are no longer needed
|
* Tint tiles in all layers that are no longer needed
|
||||||
*
|
*
|
||||||
* @memberof DeepZoomImage
|
* @memberof DeepZoomImage
|
||||||
*/
|
*/
|
||||||
tintObsoleteTiles() {
|
tintObsoleteTiles() {
|
||||||
Object.keys(this.tileLayers).forEach((key) => {
|
Object.keys(this.tileLayers).forEach(key => {
|
||||||
let tiles = this.tileLayers[key]
|
let tiles = this.tileLayers[key]
|
||||||
tiles.untintTiles()
|
tiles.untintTiles()
|
||||||
if (!tiles.keep) {
|
if (!tiles.keep) {
|
||||||
@ -908,7 +928,7 @@ export class DeepZoomImage extends PIXI.Container {
|
|||||||
* @memberof DeepZoomImage
|
* @memberof DeepZoomImage
|
||||||
*/
|
*/
|
||||||
destroyUnneededTiles() {
|
destroyUnneededTiles() {
|
||||||
Object.keys(this.tileLayers).forEach((key) => {
|
Object.keys(this.tileLayers).forEach(key => {
|
||||||
let tiles = this.tileLayers[key]
|
let tiles = this.tileLayers[key]
|
||||||
if (!tiles.keep) {
|
if (!tiles.keep) {
|
||||||
tiles.destroyUnneededTiles()
|
tiles.destroyUnneededTiles()
|
||||||
@ -922,7 +942,7 @@ export class DeepZoomImage extends PIXI.Container {
|
|||||||
* @memberof DeepZoomImage
|
* @memberof DeepZoomImage
|
||||||
*/
|
*/
|
||||||
destroyObsoleteTiles() {
|
destroyObsoleteTiles() {
|
||||||
Object.keys(this.tileLayers).forEach((key) => {
|
Object.keys(this.tileLayers).forEach(key => {
|
||||||
let tiles = this.tileLayers[key]
|
let tiles = this.tileLayers[key]
|
||||||
if (!tiles.keep) {
|
if (!tiles.keep) {
|
||||||
tiles.destroyObsoleteTiles()
|
tiles.destroyObsoleteTiles()
|
||||||
@ -937,7 +957,7 @@ export class DeepZoomImage extends PIXI.Container {
|
|||||||
* @memberof DeepZoomImage
|
* @memberof DeepZoomImage
|
||||||
*/
|
*/
|
||||||
destroyTiles() {
|
destroyTiles() {
|
||||||
Object.keys(this.tileLayers).forEach((key) => {
|
Object.keys(this.tileLayers).forEach(key => {
|
||||||
let tiles = this.tileLayers[key]
|
let tiles = this.tileLayers[key]
|
||||||
if (!tiles.keep) {
|
if (!tiles.keep) {
|
||||||
tiles.destroyTiles(this.quadTrees)
|
tiles.destroyTiles(this.quadTrees)
|
||||||
@ -949,7 +969,7 @@ export class DeepZoomImage extends PIXI.Container {
|
|||||||
* @param {number} level - The zoom level of the grid
|
* @param {number} level - The zoom level of the grid
|
||||||
*/
|
*/
|
||||||
tintTilesBelowLevel(level) {
|
tintTilesBelowLevel(level) {
|
||||||
Object.keys(this.tileLayers).forEach((key) => {
|
Object.keys(this.tileLayers).forEach(key => {
|
||||||
let tiles = this.tileLayers[key]
|
let tiles = this.tileLayers[key]
|
||||||
if (tiles.level < level) {
|
if (tiles.level < level) {
|
||||||
tiles.tintTiles(this.quadTrees)
|
tiles.tintTiles(this.quadTrees)
|
||||||
@ -989,7 +1009,7 @@ export class DeepZoomImage extends PIXI.Container {
|
|||||||
this.fastLoads += 1
|
this.fastLoads += 1
|
||||||
this.populateTiles(currentTiles, this.currentLevel, {
|
this.populateTiles(currentTiles, this.currentLevel, {
|
||||||
onlyone: false,
|
onlyone: false,
|
||||||
about: event.about,
|
about: event.about
|
||||||
})
|
})
|
||||||
if (this.fastLoads == 3) {
|
if (this.fastLoads == 3) {
|
||||||
this.fastLoads = 0
|
this.fastLoads = 0
|
||||||
|
@ -40,7 +40,7 @@ export class TileLoader {
|
|||||||
if (this.loaded.has(url)) this.loaded.delete(url)
|
if (this.loaded.has(url)) this.loaded.delete(url)
|
||||||
if (this.loading.has(url)) this.loading.delete(url)
|
if (this.loading.has(url)) this.loading.delete(url)
|
||||||
//Tile.unschedule(url)
|
//Tile.unschedule(url)
|
||||||
this.loadQueue = this.loadQueue.filter((item) => item != url)
|
this.loadQueue = this.loadQueue.filter(item => item != url)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cancels loading by clearing the load queue **/
|
/** Cancels loading by clearing the load queue **/
|
||||||
@ -246,7 +246,7 @@ export class RequestTileLoader extends TileLoader {
|
|||||||
let xhr = new XMLHttpRequest()
|
let xhr = new XMLHttpRequest()
|
||||||
xhr.open('GET', url, false)
|
xhr.open('GET', url, false)
|
||||||
xhr.responseType = 'arraybuffer'
|
xhr.responseType = 'arraybuffer'
|
||||||
xhr.onload = (e) => {
|
xhr.onload = e => {
|
||||||
let CompressedImage = PIXI.compressedTextures.CompressedImage
|
let CompressedImage = PIXI.compressedTextures.CompressedImage
|
||||||
let compressed = CompressedImage.loadFromArrayBuffer(xhr.response, url)
|
let compressed = CompressedImage.loadFromArrayBuffer(xhr.response, url)
|
||||||
let base = new PIXI.BaseTexture(compressed)
|
let base = new PIXI.BaseTexture(compressed)
|
||||||
@ -317,10 +317,10 @@ export class RequestTileLoader extends TileLoader {
|
|||||||
export class WorkerTileLoader extends TileLoader {
|
export class WorkerTileLoader extends TileLoader {
|
||||||
constructor(tiles, workerPath) {
|
constructor(tiles, workerPath) {
|
||||||
super(tiles)
|
super(tiles)
|
||||||
|
this.debug = false
|
||||||
let worker = (this.worker = new Worker(workerPath))
|
let worker = (this.worker = new Worker(workerPath))
|
||||||
|
|
||||||
worker.onmessage = (event) => {
|
worker.onmessage = event => {
|
||||||
if (event.data.success) {
|
if (event.data.success) {
|
||||||
let { url, col, row, buffer } = event.data
|
let { url, col, row, buffer } = event.data
|
||||||
//console.log("WorkerTileLoader.loaded", url, buffer)
|
//console.log("WorkerTileLoader.loaded", url, buffer)
|
||||||
@ -353,11 +353,13 @@ export class WorkerTileLoader extends TileLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cancel() {
|
cancel() {
|
||||||
|
if (this.debug) console.log('canceling worker')
|
||||||
super.cancel()
|
super.cancel()
|
||||||
this.worker.postMessage({ command: 'abort' })
|
this.worker.postMessage({ command: 'abort' })
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
if (this.debug) console.log('destroying worker')
|
||||||
this.worker.postMessage({ command: 'abort' })
|
this.worker.postMessage({ command: 'abort' })
|
||||||
this.worker.terminate()
|
this.worker.terminate()
|
||||||
this.worker = null
|
this.worker = null
|
||||||
|
@ -1,35 +1,59 @@
|
|||||||
let loadQueue = []
|
let loadQueue = []
|
||||||
|
let requestPool = []
|
||||||
let pendingRequests = new Map()
|
let pendingRequests = new Map()
|
||||||
|
let requestCount = 0
|
||||||
const batchSize = 8
|
const batchSize = 8
|
||||||
const debug = false
|
const debug = false
|
||||||
|
|
||||||
|
console.log('tileloader.js')
|
||||||
|
function recycledXMLHttpRequest() {
|
||||||
|
// https://nullprogram.com/blog/2013/02/08/
|
||||||
|
if (requestPool.length > 0) {
|
||||||
|
return requestPool.pop()
|
||||||
|
}
|
||||||
|
requestCount += 1
|
||||||
|
if (debug) console.log('create XMLHttpRequest', requestCount)
|
||||||
|
let xhr = new XMLHttpRequest()
|
||||||
|
return xhr
|
||||||
|
}
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
while (loadQueue.length > 0 && pendingRequests.size < batchSize) {
|
while (loadQueue.length > 0 && pendingRequests.size < batchSize) {
|
||||||
let tile = loadQueue.shift()
|
let tile = loadQueue.shift()
|
||||||
let [col, row, url] = tile
|
let [col, row, url] = tile
|
||||||
let xhr = new XMLHttpRequest()
|
let xhr = recycledXMLHttpRequest()
|
||||||
xhr.responseType = 'arraybuffer'
|
xhr.onload = event => {
|
||||||
xhr.onload = (event) => {
|
|
||||||
pendingRequests.delete(url)
|
|
||||||
let buffer = xhr.response
|
let buffer = xhr.response
|
||||||
postMessage({ success: true, url, col, row, buffer }, [buffer])
|
postMessage({ success: true, url, col, row, buffer }, [buffer])
|
||||||
}
|
|
||||||
xhr.onerror = (event) => {
|
|
||||||
pendingRequests.delete(url)
|
pendingRequests.delete(url)
|
||||||
|
}
|
||||||
|
xhr.onerror = event => {
|
||||||
let buffer = null
|
let buffer = null
|
||||||
postMessage({ success: false, url, col, row, buffer })
|
postMessage({ success: false, url, col, row, buffer })
|
||||||
|
pendingRequests.delete(url)
|
||||||
}
|
}
|
||||||
|
xhr.onreadystatechange = () => {
|
||||||
|
// In local files, status is 0 upon success in Mozilla Firefox
|
||||||
|
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||||
|
pendingRequests.delete(url)
|
||||||
|
requestPool.push(xhr)
|
||||||
|
if (debug) console.log('resuse XMLHttpRequest')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (debug) console.log('open XMLHttpRequest')
|
||||||
xhr.open('GET', url, true)
|
xhr.open('GET', url, true)
|
||||||
|
xhr.responseType = 'arraybuffer'
|
||||||
|
if (debug) console.log('send XMLHttpRequest')
|
||||||
xhr.send()
|
xhr.send()
|
||||||
pendingRequests.set(url, xhr)
|
pendingRequests.set(url, xhr)
|
||||||
}
|
}
|
||||||
if (loadQueue.length > 0) setTimeout(load, 1000 / 120)
|
if (loadQueue.length > 0) setTimeout(load, 1000 / 120)
|
||||||
else {
|
else {
|
||||||
if (debug) console.log('Ready')
|
if (debug) console.log('tileloader ready')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.onmessage = (event) => {
|
self.onmessage = event => {
|
||||||
let msg = event.data
|
let msg = event.data
|
||||||
switch (msg.command) {
|
switch (msg.command) {
|
||||||
case 'load':
|
case 'load':
|
||||||
@ -40,10 +64,13 @@ self.onmessage = (event) => {
|
|||||||
break
|
break
|
||||||
case 'abort':
|
case 'abort':
|
||||||
loadQueue = []
|
loadQueue = []
|
||||||
|
requestPool = []
|
||||||
for (let xhr of pendingRequests.values()) {
|
for (let xhr of pendingRequests.values()) {
|
||||||
xhr.abort()
|
xhr.abort()
|
||||||
}
|
}
|
||||||
if (debug) console.log('Abort')
|
pendingRequests.clear()
|
||||||
|
if (debug) console.log('tileloader aborted')
|
||||||
|
console.log('tileloader aborted')
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
console.warn('Unknown worker command: ' + msg.command)
|
console.warn('Unknown worker command: ' + msg.command)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user