369 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			369 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import 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
 | 
						|
 | 
						|
        //Tile.schedule(url)
 | 
						|
        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)
 | 
						|
        //Tile.unschedule(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()
 | 
						|
        }
 | 
						|
        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)
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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.Loader()
 | 
						|
        this.loader.onLoad.add(this._onLoaded.bind(this))
 | 
						|
        this.loader.onError.add(this._onError.bind(this))
 | 
						|
        if (compression) {
 | 
						|
            this.loader.use(PIXI.compressedTextures.ImageParser.use)
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    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
 | 
						|
 | 
						|
        //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)
 | 
						|
            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 url = resource.url
 | 
						|
            Tile.lateTexture(url, texture)
 | 
						|
            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.from('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, workerPath) {
 | 
						|
        super(tiles)
 | 
						|
        this.debug = false
 | 
						|
        let worker = (this.worker = new Worker(workerPath))
 | 
						|
 | 
						|
        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 = PIXI.compressedTextures.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() {
 | 
						|
        if (this.debug) console.log('canceling worker')
 | 
						|
        super.cancel()
 | 
						|
        this.worker.postMessage({ command: 'abort' })
 | 
						|
    }
 | 
						|
 | 
						|
    destroy() {
 | 
						|
        if (this.debug) console.log('destroying worker')
 | 
						|
        this.worker.postMessage({ command: 'abort' })
 | 
						|
        this.worker.terminate()
 | 
						|
        this.worker = null
 | 
						|
        super.destroy()
 | 
						|
    }
 | 
						|
}
 |