create-dan
Version:
Dan frameworks
229 lines (192 loc) • 5.31 kB
JavaScript
import { getAssetLoader, LoaderAssetStatus } from './loaders'
import uniqid from 'uniqid'
import { addRafOnce } from 'dan/raf'
import Signal from 'signal'
/**
* Assets queue
*/
export default class AssetQueue {
/**
* Constructor
* @param {Object} [options] - Options (cache, size)
*/
constructor({ cache = false, size = 0, autoStart = true, simultaneous = 0 } = {}) {
this._assets = {}
this.cache = cache
this._autoStart = autoStart
this._needsUpdate = false
// Progress
this._size = size
this._autoSize = size < 1
// Queue
this.simultaneous = simultaneous
this._numberOfLoading = 0
// Events
this.onProgress = new Signal()
this.onComplete = new Signal()
this.onError = new Signal()
}
/**
* Alias for removeListener
*/
off() {
this.removeListener.apply(this, arguments);
}
/**
* Add a new asset to load
* @param {*} header - Asset adress (URL, function, ...)
* @param {Object} [options] - Asset options (cache, id, size)
*/
add(header, options = {}) {
// Id
options.id = typeof options.id === 'string'
?options.id
:typeof header === 'string'
?header
:uniqid()
if(this._assets[options.id]) {
throw new Error('Asset ID "'+options.id+'" is already in the queue')
}
// Cache
options.cache = typeof options.cache === 'boolean'
?options.cache
:this.cache
// Get asset loader
const loader = getAssetLoader(header, options)
this._assets[options.id] = loader
// Size
if(this._autoSize) {
this._size += loader.size
}
// Load
const loading = loader.loading
// Auto start
if(this._autoStart) {
this._update()
}
// Watch
loading.then((body) => {
this._update()
}).catch((error) => {
this.onError.dispatch(error)
this._update()
})
return loading
}
/**
* Run the queue
*/
start() {
this._autoStart = true
if(this._numberOfLoading < this.simultaneous || this.simultaneous === 0) {
let assets = []
for(let id in this._assets) {
assets.push(this._assets[id])
}
assets = assets.filter(asset => asset.status === LoaderAssetStatus.Waiting).sort((a, b) => a.priority - b.priority)
const length = this.simultaneous === 0
?assets.length
:Math.min(this.simultaneous - this._numberOfLoading, assets.length)
const next = () => {
this._numberOfLoading--
if(this._autoStart) {
this.start()
}
}
for(let i = 0; i < length; i++) {
this._numberOfLoading++
assets[i].load().then(next).catch(next)
}
}
}
/**
* Stop the queue
*/
stop() {
this._autoStart = false
}
/**
* Control group status
*/
_update() {
// Skip if queue was already waiting for update
if(this._needsUpdate) {
return
}
this._needsUpdate = true
addRafOnce(() => {
this._needsUpdate = false
// AutoStart
if(this._autoStart) {
this.start()
}
// Percent
const percent = this.progress
// Progress
this.onProgress.dispatch(percent)
// Complete
if(percent >= 1) {
this.onComplete.dispatch(this.assets)
}
})
}
/**
* Remove an asset to the queue
* @param {String} id - Asset id
*/
remove(id) {
delete this._assets[id];
}
/**
* Total items size to load
* @return {number}
*/
get progress() {
let progress = 0,
asset
for(let id in this._assets) {
asset = this._assets[id]
// Complete or error
if(asset.status === LoaderAssetStatus.Complete || asset.status === LoaderAssetStatus.Error) {
progress += asset.size
}
}
// Percent
return progress / this._size
}
/**
* Get an asset by id
* @param {String} id - Asset id
* @returns {*} - Asset body
*/
get(id) {
const asset = this._assets[id]
return asset?asset.body:null
}
/**
* Get asset attributes
* @param {*} id - Asset id
* @return {Object} Attributes
*/
attributes(id) {
const asset = this._assets[id]
if(asset) {
return asset.attributes
}
else {
return null
}
}
/**
* Get all assets
* @returns {Object}
*/
get assets() {
const assets = {}
for(let id in this._assets) {
assets[id] = this._assets[id].body
}
return assets
}
}
export {AssetQueue}