import { Utils } from '../common/Utils';
import { Bundle } from '../loaders/Bundle';
import { BaseComponent } from './BaseComponent';
import { PrefabBundleLoader } from '../loaders/PrefabBundleLoader';
import { ModelBundleLoader } from '../loaders/ModelBundleLoader';
const __ = {
private: Symbol('private'),
}
/**
* @class AppBundleComponent
* The application bundle component.
* @memberof THING
* @extends THING.BaseComponent
*/
class AppBundleComponent extends BaseComponent {
/**
* The bundle loader interface for application.
*/
constructor() {
super();
this[__.private] = {};
let _private = this[__.private];
// Url + bundles
_private.bundlesMap = new Map();
_private.loaders = {
'prefab': new PrefabBundleLoader(),
'model': new ModelBundleLoader()
};
}
// #region Private
_collectBundles(urls, options) {
let _private = this[__.private];
// Make all url to string array type
if (Utils.isString(urls)) {
urls = [urls];
}
return urls.map(url => {
let key = url;
let bundles = _private.bundlesMap.get(key);
if (!bundles) {
// Prepare to create new bundle with key(url)
bundles = [];
_private.bundlesMap.set(key, bundles);
}
let bundle = new Bundle({ url });
bundles.push(bundle);
return bundle;
});
}
_unloadBundle(bundle) {
let _private = this[__.private];
let type = bundle.info.type;
let loader = _private.loaders[type];
if (loader) {
if (Utils.isFunction(loader.onUnload)) {
loader.onUnload(bundle);
}
}
bundle.dispose();
}
_loadBundles(bundles, options) {
let _private = this[__.private];
return bundles.waitForEachAsync(
bundle => {
if (bundle.loaded) {
return bundle.reload(_private.loaders, options);
}
else {
return bundle.load(_private.loaders, options);
}
},
(ev) => {
}
);
}
// #endregion
// #region BaseComponent Interface
onBeforeRemove() {
let _private = this[__.private];
_private.bundlesMap.forEach(bundles => {
bundles.forEach(bundle => {
this._unloadBundle(bundle);
});
});
_private.bundlesMap.clear();
for (let key in _private.loaders) {
let loader = _private.loaders[key];
if (loader.onDispose) {
loader.onDispose();
}
}
_private.loades = {};
}
// #endregion
/**
* Register bundle loader.
* @param {String} type The bundle type.
* @param {Object} loader The loader object.
*/
registerBundleLoader(type, loader) {
let _private = this[__.private];
_private.loaders[type] = loader;
}
/**
* Load bundle.
* @param {String|Array<String>} urls The bundle resource url(s).
* @param {Object} options The options.
* @returns {Object}
* @example
* let bundle = app.loadBundle('./bundle/scene-bundle');
* bundle.waitForComplete().then(() => {
* console.log(bundle.info.name);
* bundle.release();
* });
*/
loadBundle(urls, options = {}) {
// Collect bundle(s)
let bundles = this._collectBundles(urls, options);
// Start to load bundle(s)
this._loadBundles(bundles, options);
// Feedback bundle(s), these may be still in loading state
if (bundles.length === 1) {
return bundles[0];
}
else {
return bundles;
}
}
/**
* Load bundle in async mode.
* @param {String|Array<String>} urls The bundle resource url(s).
* @param {Object} options The options.
* @returns {Promise<any>}
* @example
* let bundle = await app.loadBundleAsync('./bundle/scene-bundle');
* console.log(bundle.info.name);
* bundle.release();
*/
loadBundleAsync(urls, options = {}) {
return new Promise((resolve, reject) => {
// Build bundle(s)
let bundles = this._collectBundles(urls, options);
// Start to load bundle(s)
this._loadBundles(bundles, options).then(
() => {
if (bundles.length === 1) {
return resolve(bundles[0]);
}
else {
return resolve(bundles);
}
},
(ex) => {
reject(ex);
}
);
});
}
/**
* Unload bundle by url.
* @param {String|Array<String>} urls The bundle resource url(s).
* @example
* THING.App.current.unloadBundle('./bundle/scene-bundle');
*/
unloadBundleByUrl(urls) {
let _private = this[__.private];
// Make all url to string array type
if (Utils.isString(urls)) {
urls = [urls];
}
urls.forEach(url => {
let bundles = _private.bundlesMap.get(url);
if (!bundles) {
return;
}
// To prevent map delete conflict we need to copy bundles here
bundles.slice(0).forEach(bundle => {
this._unloadBundle(bundle);
});
_private.bundlesMap.delete(url);
});
}
/**
* Unload bundle
* @param {Bundle} bundle The bundle
* @example THING.App.current.unloadBundle(bundle);
*/
unloadBundle(bundle) {
if (Utils.isString(bundle)) {
Utils.warn('Please use "unloadBundleByUrl" to unload!');
this.unloadBundleByUrl(bundle);
return;
}
else if (Utils.isArray(bundle)) {
const first = bundle[0];
if (Utils.isString(first)) {
Utils.warn('Please use "unloadBundleByUrl" to unload!');
this.unloadBundleByUrl(bundle);
return;
}
}
if (!bundle || !bundle.isBundle) {
return;
}
let _private = this[__.private];
const url = bundle.url;
let bundles = _private.bundlesMap.get(url);
if (bundles) {
for (let i = 0; i < bundles.length; i++) {
const element = bundles[i];
if (element == bundle) {
bundles.splice(i, 1);
break;
}
}
}
this._unloadBundle(bundle);
}
}
AppBundleComponent.exportFunctions = [
'registerBundleLoader',
'loadBundle',
'loadBundleAsync',
'unloadBundle',
'unloadBundleByUrl'
];
export { AppBundleComponent }