Source: components/BlueprintComponent.js

import { Utils } from '../common/Utils';
import { BaseComponent } from './BaseComponent';

const __ = {
	private: Symbol('private'),
}

/**
 * @class BlueprintComponent
 * The blueprint component.
 * @memberof THING
 * @extends THING.BaseComponent
 * @public
 */
class BlueprintComponent extends BaseComponent {

	static mustCopyWithInstance = true;

	/**
	 * To load blueprint resource(s) and run them.
	 */
	constructor() {
		super();

		this[__.private] = {};
		let _private = this[__.private];

		_private.pendingPromise = null;
		_private.resources = [];

		_private.debugger = null;
	}

	onRemove() {
		let _private = this[__.private];

		if (_private.pendingPromise) {
			_private.pendingPromise.then(() => {
				_private.resources.forEach(resource => {
					resource.dispose();
				});
				_private.resources.length = 0;

				if (_private.debugger) {
					_private.debugger.dispose();
					_private.debugger = null;
				}

				_private.pendingPromise = null;

				super.onRemove();
			});
		}
	}

	// #region Private

	// #endregion

	/**
	 * @typedef {Object} BlueprintComponentLoadArgs
	 * @property {String|Array<String>} url The resource URL(s).
	 * @property {Object|Array<Object>} data The json data(s).
	 */

	/**
	 * Load from URL or data.
	 * @param {BlueprintComponentLoadArgs} options The options.
	 * @returns {THING.BlueprintComponent}
	 * @public
	 * @example
	 * let object = new THING.Object3D();
	 * let blueprints = await object.blueprint.load({ url: './blueprints/myBP.json' });
	 * // @expect(blueprints.length == 1)
	 */
	load(options = {}) {
		let _private = this[__.private];

		let url = options['url'];
		let data = options['data'] || [];

		let resourceManager = this.app.resourceManager;

		_private.pendingPromise = new Promise((resolve, reject) => {
			let blueprints = [];
			// Get data through url when has url(can use cache in async mode).
			if (url) {
				if (Utils.isString(url)) {
					url = [url];
				}

				url.forEachAsync((_url) => {
					return resourceManager.loadBlueprintAsync(_url).then(datas => {
						datas.forEach(data => {
							let blueprint = this._loadBlueprintData(data);
							blueprints.push(blueprint);
						});
					}, (err) => {
						reject(err);
					})
				}).then(() => {
					resolve(blueprints);
				});
			}
			// Get data through url(in sync mode).
			else if (data) {
				if (data.info) {
					let bodyData = resourceManager.getBlueprintBodyData(data);

					// Load blueprint resource(s)
					bodyData.forEach(body => {
						let blueprint = this._loadBlueprintData(body);
						blueprints.push(blueprint);
					});
				}
				else {
					let blueprint = this._loadBlueprintData(data);
					blueprints.push(blueprint);
				}
				resolve(blueprints);
			}
		});

		return _private.pendingPromise;
	}

	_loadBlueprintData(data) {
		let _private = this[__.private];
		let resources = _private.resources;

		let blueprint = Utils.createObject('Blueprint', { app: this.app, object: this.object });
		resources.push(blueprint);

		blueprint.load(data);
		return blueprint;
	}

	/**
	 * Run all blueprints.
	 * @public
	 */
	run() {
		let _private = this[__.private];
		if (_private.pendingPromise) {
			_private.pendingPromise.then(() => {
				_private.resources.forEach(resource => {
					resource.getRunner().start();
				});
			}, (err) => {
				Utils.error(err)
			});
		}
	}

	/**
	 * clear all blueprints.
	 * @public
	 */
	clear() {
		let _private = this[__.private];
		if (_private.pendingPromise) {
			_private.pendingPromise.then(() => {
				_private.resources.forEach(resource => {
					resource.dispose();
				});
				_private.resources = []
			});
		}
	}

	/**
	 * Stop all blueprints.
	 * @public
	 */
	stop() {
		let _private = this[__.private];
		if (_private.pendingPromise) {
			_private.pendingPromise.then(() => {
				_private.resources.forEach(resource => {
					resource.getRunner().stop();
				});
			});
		}
	}

	/**
	 * Set variable by name.
	 * @param {String} name The variable name.
	 * @param {*} value The variable value.
	 * @public
	 */
	setVar(name, value) {
		let _private = this[__.private];
		if (_private.pendingPromise) {
			_private.pendingPromise.then(() => {
				_private.resources.forEach(resource => {
					resource.setVar(name, value);
				});
			});
		}
	}

	/**
	 * Set variables.
	 * @param {Object} value The variables.
	 * @public
	 */
	setVars(value) {
		let _private = this[__.private];
		if (_private.pendingPromise) {
			_private.pendingPromise.then(() => {
				_private.resources.forEach(resource => {
					resource.setVars(value);
				});
			});
		}
	}

	/**
	 * Register event.
	 * @param {String} type The event type.
	 * @param {Function} callback The callback function.
	 * @public
	 */
	addEventListener(type, callback) {
		let _private = this[__.private];
		if (_private.pendingPromise) {
			_private.pendingPromise.then(() => {
				_private.resources.forEach(resource => {
					resource.addEventListener(type, callback);
				});
			});
		}
	}

	/**
	 * Unregister event.
	 * @param {String} type The event type.
	 * @param {Function} callback The callback function.
	 * @public
	 */
	removeEventListener(type, callback) {
		let _private = this[__.private];
		if (_private.pendingPromise) {
			_private.pendingPromise.then(() => {
				_private.resources.forEach(resource => {
					resource.removeEventListener(type, callback);
				});
			});
		}
	}

	/**
	 * Trigger event.
	 * @param {String} type The event type.
	 * @param {Object} event The event info.
	 * @public
	 */
	triggerEvent(type, event) {
		let _private = this[__.private];
		if (_private.pendingPromise) {
			_private.pendingPromise.then(() => {
				_private.resources.forEach(resource => {
					resource.triggerEvent(type, event);
				});
			});
		}
	}

	getDebugger() {
		let _private = this[__.private];

		_private.debugger = _private.debugger || Utils.createObject('BlueprintDebugger');

		_private.resources.forEach(resource => {
			resource.setDebugger(_private.debugger);
		});

		return _private.debugger;
	}

}

export { BlueprintComponent }