Source: loader/LoadQueue.js

import Class from '../core/Class';
import EventMixin from '../core/EventMixin';
import Loader from './Loader';
import log from '../utils/log';

/**
 * 队列加载器,用于批量加载
 * @class
 * @mixes EventMixin
 * @fires complete 完成事件
 * @fires load 加载事件
 * @fires error 错误事件
 * @example
 * var loadQueue = new Hilo3d.LoadQueue([{
 *     type: 'CubeTexture',
 *     images: [
 *         '//gw.alicdn.com/tfs/TB1Ss.ORpXXXXcNXVXXXXXXXXXX-2048-2048.jpg_960x960.jpg',
 *         '//gw.alicdn.com/tfs/TB1YhUDRpXXXXcyaXXXXXXXXXXX-2048-2048.jpg_960x960.jpg',
 *         '//gw.alicdn.com/tfs/TB1Y1MORpXXXXcpXVXXXXXXXXXX-2048-2048.jpg_960x960.jpg',
 *         '//gw.alicdn.com/tfs/TB1ZgAqRpXXXXa0aFXXXXXXXXXX-2048-2048.jpg_960x960.jpg',
 *         '//gw.alicdn.com/tfs/TB1IVZNRpXXXXaNXFXXXXXXXXXX-2048-2048.jpg_960x960.jpg',
 *         '//gw.alicdn.com/tfs/TB1M3gyRpXXXXb9apXXXXXXXXXX-2048-2048.jpg_960x960.jpg'
 *     ]
 * }, {
 *     src: '//ossgw.alicdn.com/tmall-c3/tmx/0356679fd543809bba95dfaea32e1d45.gltf'
 * }]).on('complete', function () {
 *     var result = loadQueue.getAllContent();
 *     var box = new Hilo3d.Mesh({
 *         geometry: geometry,
 *         material: new Hilo3d.BasicMaterial({
 *             lightType: 'NONE',
 *             cullFaceType: Hilo3d.constants.FRONT,
 *             diffuse: result[0]
 *         })
 *     }).addTo(stage);
 *     box.setScale(20);
 *     var material = new Hilo3d.BasicMaterial({
 *         diffuse: new Hilo3d.Color(0, 0, 0),
 *         skyboxMap: result[0],
 *         refractRatio: 1/1.5,
 *         refractivity: 0.8,
 *         reflectivity: 0.2
 *     });
 *     var model = result[1];
 *     model.node.setScale(0.001);
 *     model.meshes.forEach(function (m) {
 *         m.material = material;
 *     });
 *     stage.addChild(model.node);
 * }).start();
 */
const LoadQueue = Class.create(/** @lends LoadQueue.prototype */ {
    Mixes: EventMixin,
    /**
     * @default true
     * @type {boolean}
     */
    isLoadQueue: true,
    /**
     * @default LoadQueue
     * @type {string}
     */
    className: 'LoadQueue',
    Statics: {
        /**
         * 给LoadQueue类添加扩展Loader
         * @memberOf LoadQueue
         * @static
         * @param {string} ext 资源扩展,如gltf, png 等
         * @param {BasicLoader} LoaderClass 用于加载的类,需要继承BasicLoader
         */
        addLoader(ext, LoaderClass) {
            log.warn('LoadQueue.addLoader is duplicated, please use Loader.addLoader');
            Loader.addLoader(ext, LoaderClass);
        }
    },
    /**
     * @constructs
     * @param {Array} [source] 需要加载的资源列表
     */
    constructor(source) {
        this._source = [];
        this.add(source);
    },

    /**
     * 最大并发连接数
     * @default 2
     * @type {number}
     */
    maxConnections: 2,

    _source: null,
    _loaded: 0,
    _connections: 0,
    _currentIndex: -1,

    /**
     * 添加需要加载的资源
     *
     * @param {object} source 资源信息
     * @param {string} source.src 资源地址
     * @param {string} [source.id] 资源id
     * @param {string} [source.type] 资源类型,对应ext,不传的话自动根据src来获取
     * @param {number} [source.size] 资源大小,用于精确计算当前加载进度
     */
    add(source) {
        if (source) {
            source = Array.isArray(source) ? source : [source];
            this._source = this._source.concat(source);
        }
        return this;
    },
    /**
     * 获取指定id的资源
     *
     * @param {string} id id
     * @return {object} 返回对应的资源信息
     */
    get(id) {
        if (!id) {
            return null;
        }
        const source = this._source;
        for (let i = 0; i < source.length; i++) {
            let item = source[i];
            if (item.id === id || item.src === id) {
                return item;
            }
        }
        return null;
    },
    /**
     * 获取指定id加载完后的数据
     *
     * @param {string} id id
     * @return {object} 加载完的结果
     */
    getContent(id) {
        const item = this.get(id);
        return item && item.content;
    },
    /**
     * 开始加载资源
     * @return {LoadQueue} 返回this
     */
    start() {
        if (!this._loader) {
            this._loader = new Loader();
        }
        this._loadNext();
        return this;
    },

    _loadNext() {
        const source = this._source;
        const len = source.length;

        // all items loaded
        if (this._loaded >= len) {
            this.fire('complete');
            return;
        }

        if (this._currentIndex < len - 1 && this._connections < this.maxConnections) {
            let index = ++this._currentIndex;
            let item = source[index];

            this._connections++;
            this._loader.load(item).then((data) => {
                this._onItemLoad(index, data);
            }, (err) => {
                this._onItemError(index, err);
            });
        }
    },

    _onItemLoad(index, content) {
        const item = this._source[index];
        item.loaded = true;
        item.content = content;
        this._connections--;
        this._loaded++;
        this.fire('load', item);
        this._loadNext();
    },

    _onItemError(index, e) {
        const item = this._source[index];
        item.error = e;
        this._connections--;
        this._loaded++;
        this.fire('error', item);
        this._loadNext();
    },
    getSize(loaded) {
        let size = 0;
        const source = this._source;
        for (let i = 0; i < source.length; i++) {
            const item = source[i];
            size += (loaded ? item.loaded && item.size : item.size) || 0;
        }
        return size;
    },
    /**
     * 获取当前已经加载完的资源数量
     * @return {number}
     */
    getLoaded() {
        return this._loaded;
    },
    /**
     * 获取需要加载的资源总数
     * @return {number}
     */
    getTotal() {
        return this._source.length;
    },
    /**
     * 获取加载的所有资源结果
     *
     * @return {Array} 加载的所有资源结果
     */
    getAllContent() {
        return this._source.map(r => r.content);
    }
});

export default LoadQueue;