Restructured library.
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
import { deepZoomTileCache, Tile } from './tile.js'
|
||||
|
||||
/**
|
||||
* A Tile Loader component that can be plugged into a Tiles Layer.
|
||||
*/
|
||||
export class TileLoader {
|
||||
constructor(tiles) {
|
||||
this.debug = false
|
||||
this.tiles = tiles
|
||||
this.setup()
|
||||
}
|
||||
|
||||
/** Setup collections and instance vars. */
|
||||
setup() {
|
||||
this.map = new Map() // Map {url : [ col, row]}
|
||||
this.loading = new Set() // Set url
|
||||
this.loaded = new Map() // Map {url : sprite }
|
||||
this.loadQueue = []
|
||||
}
|
||||
|
||||
/** Schedules a tile url for loading. The loading itself must be triggered
|
||||
by a call to loadOneTile or loadAll
|
||||
|
||||
* @param {String} url - the url of the texture / tile
|
||||
* @param {Number} col - the tile col
|
||||
* @param {Number} row - the tile row
|
||||
**/
|
||||
schedule(url, col, row) {
|
||||
if (this.loaded.has(url)) return false
|
||||
if (this.loading.has(url)) return false
|
||||
this.map.set(url, [col, row])
|
||||
this.loading.add(url)
|
||||
this.loadQueue.push(url)
|
||||
return true
|
||||
}
|
||||
|
||||
unschedule(url) {
|
||||
if (this.loaded.has(url)) this.loaded.delete(url)
|
||||
if (this.loading.has(url)) this.loading.delete(url)
|
||||
this.loadQueue = this.loadQueue.filter(item => item != url)
|
||||
}
|
||||
|
||||
/** Cancels loading by clearing the load queue **/
|
||||
cancel() {
|
||||
this.loadQueue = []
|
||||
this.loading.clear()
|
||||
}
|
||||
|
||||
/** Destroys alls collections. **/
|
||||
destroy() {
|
||||
this.setup()
|
||||
}
|
||||
|
||||
/** Private method. Informs the tile layer about a texture for a given url.
|
||||
* Creates the sprite for the loaded texture and informs the tile layer.
|
||||
* @param {String} url - the url
|
||||
* @param {Object} texture - the loaded resource
|
||||
**/
|
||||
_textureAvailable(url, col, row, texture) {
|
||||
let tile = this.loaded.get(url)
|
||||
if (tile != null) {
|
||||
console.warn("Tile already loaded")
|
||||
tile.unregister()
|
||||
}
|
||||
tile = new Tile(texture, url)
|
||||
this.loaded.set(url, tile)
|
||||
this.tiles.tileAvailable(tile, col, row, url)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the PIXI Loader but can be replaced with othe loaders implementing
|
||||
* the public methods without underscore.
|
||||
* Calls the Tiles.tileAvailable method if the texture is available.
|
||||
**/
|
||||
export class PIXITileLoader extends TileLoader {
|
||||
|
||||
constructor(tiles, compression) {
|
||||
super(tiles)
|
||||
this.destroyed = false
|
||||
this.loader = new PIXI.loaders.Loader()
|
||||
this.loader.on('load', this._onLoaded.bind(this))
|
||||
this.loader.on('error', this._onError.bind(this))
|
||||
if (compression) {
|
||||
this.loader.pre(PIXI.compressedTextures.imageParser())
|
||||
}
|
||||
}
|
||||
|
||||
schedule(url, col, row) {
|
||||
// Overwritten schedule to avoid BaseTexture and Texture already loaded errors.
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
let texture = PIXI.utils.TextureCache[url]
|
||||
if (texture) {
|
||||
if (this.debug) console.log('Texture already loaded', texture)
|
||||
this._textureAvailable(url, col, row, texture)
|
||||
return false
|
||||
}
|
||||
let base = PIXI.utils.BaseTextureCache[url]
|
||||
if (base) {
|
||||
if (this.debug) console.log('BaseTexture already loaded', base)
|
||||
let texture = new PIXI.Texture(base)
|
||||
this._textureAvailable(url, col, row, texture)
|
||||
return false
|
||||
}
|
||||
return super.schedule(url, col, row)
|
||||
}
|
||||
|
||||
/** Load one and only one of the scheduled tiles **/
|
||||
loadOneTile() {
|
||||
if (this.destroyed)
|
||||
return
|
||||
this._loadOneTile()
|
||||
}
|
||||
|
||||
/** Load all scheduled tiles **/
|
||||
loadAll() {
|
||||
if (this.destroyed)
|
||||
return
|
||||
this._loadAllTiles()
|
||||
}
|
||||
|
||||
/** Destroys the loader completly **/
|
||||
destroy() {
|
||||
this.destroyed = true
|
||||
super.destroy()
|
||||
try {
|
||||
this.loader.reset()
|
||||
} catch (error) {
|
||||
console.warn("Error while resetting loader", error)
|
||||
}
|
||||
}
|
||||
|
||||
_onError(loader, error) {
|
||||
console.warn('Cannot load', error)
|
||||
}
|
||||
|
||||
/** Private method. Called by the PIXI loader after each successfull
|
||||
* loading of a single tile.
|
||||
* Creates the sprite for the loaded texture and informs the tile layer.
|
||||
* @param {Object} loader - the loader instance
|
||||
* @param {Object} resource - the loaded resource with url and texture attr
|
||||
**/
|
||||
_onLoaded(loader, resource) {
|
||||
if (this.destroyed) {
|
||||
let texture = resource.texture
|
||||
let destroyBase = !deepZoomTileCache.has(resource.url)
|
||||
texture.destroy(destroyBase)
|
||||
console.warn("Received resource after destroy", texture)
|
||||
return
|
||||
}
|
||||
try {
|
||||
let [col, row] = this.map.get(resource.url)
|
||||
this._textureAvailable(resource.url, col, row, resource.texture)
|
||||
}
|
||||
catch (err) {
|
||||
console.warn("Texture unavailable: " + err.message)
|
||||
}
|
||||
}
|
||||
|
||||
/** Private method: loads one tile from the queue. **/
|
||||
_loadOneTile(retry = 1) {
|
||||
//console.log("_loadOneTile")
|
||||
if (this.destroyed) {
|
||||
//console.warn("_loadOneTile after destroy")
|
||||
return
|
||||
}
|
||||
if (this.loader.loading) {
|
||||
setTimeout(() => {
|
||||
this._loadOneTile()
|
||||
}, retry)
|
||||
return
|
||||
}
|
||||
if (this.loadQueue.length > 0) {
|
||||
let url = this.loadQueue.pop()
|
||||
this.loader.add(url, url)
|
||||
this.loader.load()
|
||||
}
|
||||
}
|
||||
|
||||
/** Private method: loads all tiles from the queue in batches. Batches are
|
||||
helpfull to avoid loading tiles that are no longer needed because the
|
||||
user has already zoomed to a different level.**/
|
||||
_loadAllTiles(batchSize = 8, retry = 16) {
|
||||
if (this.destroyed) {
|
||||
return
|
||||
}
|
||||
if (this.loadQueue.length > 0) {
|
||||
if (this.loader.loading) {
|
||||
//console.log("Loader busy", this.loadQueue.length)
|
||||
setTimeout(() => {
|
||||
this._loadAllTiles()
|
||||
}, retry)
|
||||
return
|
||||
}
|
||||
let i = 0
|
||||
let urls = []
|
||||
while (i < batchSize && this.loadQueue.length > 0) {
|
||||
let url = this.loadQueue.pop()
|
||||
if (!this.loaded.has(url)) {
|
||||
let resource = this.loader.resources[url]
|
||||
if (resource) {
|
||||
console.log("Resource already added", url)
|
||||
}
|
||||
else {
|
||||
urls.push(url)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
this.loader.add(urls).load(() => {
|
||||
this._loadAllTiles()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses XMLHttpRequests but can be replaced with other loaders implementing
|
||||
* the public methods without underscore.
|
||||
* Calls the Tiles.tileAvailable method if the texture is available.
|
||||
**/
|
||||
export class RequestTileLoader extends TileLoader {
|
||||
|
||||
constructor(tiles, compression) {
|
||||
super(tiles)
|
||||
this.compression = compression
|
||||
}
|
||||
|
||||
schedule(url, col, row) {
|
||||
this._load(url, col, row)
|
||||
return super.schedule(url, col, row)
|
||||
}
|
||||
|
||||
_load(url, col, row, callback = null) {
|
||||
if (this.compression) {
|
||||
let xhr = new XMLHttpRequest()
|
||||
xhr.open('GET', url, false)
|
||||
xhr.responseType = 'arraybuffer'
|
||||
xhr.onload = e => {
|
||||
let CompressedImage = PIXI.compressedTextures.CompressedImage
|
||||
let compressed = CompressedImage.loadFromArrayBuffer(
|
||||
xhr.response,
|
||||
url
|
||||
)
|
||||
let base = new PIXI.BaseTexture(compressed)
|
||||
let texture = new PIXI.Texture(base)
|
||||
this._textureAvailable(url, col, row, texture)
|
||||
if (callback) callback()
|
||||
}
|
||||
xhr.send()
|
||||
} else {
|
||||
let texture = PIXI.Texture.fromImage('assets/image.png')
|
||||
this._textureAvailable(url, col, row, texture)
|
||||
if (callback) callback()
|
||||
}
|
||||
}
|
||||
|
||||
/** Load one and only one of the scheduled tiles **/
|
||||
loadOneTile() {
|
||||
this._loadOneTile()
|
||||
}
|
||||
|
||||
/** Load all scheduled tiles **/
|
||||
loadAll() {
|
||||
this._loadAllTiles()
|
||||
}
|
||||
|
||||
/** Private method: loads one tile from the queue. **/
|
||||
_loadOneTile(retry = 1) {
|
||||
if (this.loadQueue.length > 0) {
|
||||
let url = this.loadQueue.pop()
|
||||
let [col, row] = this.map.get(url)
|
||||
this._load(url, col, row)
|
||||
}
|
||||
}
|
||||
|
||||
/** Private method: loads all tiles from the queue in batches. Batches are
|
||||
helpfull to avoid loading tiles that are no longer needed because the
|
||||
user has already zoomed to a different level.**/
|
||||
_loadAllTiles(batchSize = 8, retry = 16) {
|
||||
if (this.loadQueue.length > 0) {
|
||||
let i = 0
|
||||
let urls = []
|
||||
while (i < batchSize && this.loadQueue.length > 0) {
|
||||
let url = this.loadQueue.pop()
|
||||
if (this.debug) console.time(url)
|
||||
if (!this.loaded.has(url)) {
|
||||
urls.push(url)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
let total = urls.length
|
||||
let count = 0
|
||||
for (let url of urls) {
|
||||
let [col, row] = this.map.get(url)
|
||||
this._load(url, col, row, () => {
|
||||
count++
|
||||
if (count == total) this._loadAllTiles()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses Workers but can be replaced with other loaders implementing
|
||||
* the public methods without underscore.
|
||||
* Calls the Tiles.tileAvailable method if the texture is available.
|
||||
**/
|
||||
export class WorkerTileLoader extends TileLoader {
|
||||
|
||||
constructor(tiles) {
|
||||
super(tiles)
|
||||
let worker = this.worker = new Worker("../../lib/pixi/deepzoom/tileloader.js")
|
||||
worker.onmessage = (event) => {
|
||||
if (event.data.success) {
|
||||
let { url, col, row, buffer } = event.data
|
||||
//console.log("WorkerTileLoader.loaded", url, buffer)
|
||||
let CompressedImage = PIXI.compressedTextures.CompressedImage
|
||||
let compressed = CompressedImage.loadFromArrayBuffer(buffer, url)
|
||||
let base = new PIXI.BaseTexture(compressed)
|
||||
let texture = new PIXI.Texture(base)
|
||||
this._textureAvailable(url, col, row, texture)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadOne() {
|
||||
if (this.loadQueue.length > 0) {
|
||||
let url = this.loadQueue.pop()
|
||||
let [col, row] = this.map.get(url)
|
||||
let tile = [col, row, url]
|
||||
this.worker.postMessage({ command: "load", tiles: [tile] })
|
||||
}
|
||||
}
|
||||
|
||||
loadAll() {
|
||||
let tiles = []
|
||||
while (this.loadQueue.length > 0) {
|
||||
let url = this.loadQueue.pop()
|
||||
let [col, row] = this.map.get(url)
|
||||
tiles.push([col, row, url])
|
||||
}
|
||||
this.worker.postMessage({ command: "load", tiles })
|
||||
}
|
||||
|
||||
cancel() {
|
||||
super.cancel()
|
||||
this.worker.postMessage({ command: "abort" })
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.worker.postMessage({ command: "abort" })
|
||||
this.worker.terminate()
|
||||
this.worker = null
|
||||
super.destroy()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user