Source: core/App.js

import { StringEncoder, Flags, Callbacks } from '@uino/base-thing';
import { Utils, _getAuthData } from '../common/Utils';
import { Selector } from '../selector/Selector';
import { MathUtils } from '../math/MathUtils';
import { Camera } from '../objects/Camera';
import { ResourceManager } from '../managers/ResourceManager';
import { EventManager } from '../managers/EventManager';
import { ObjectManager } from '../managers/ObjectManager';
import { TweenManager } from '../managers/TweenManager';
import { ActionManager } from '../managers/ActionManager';
import { CreateObjectAction } from '../actions/CreateObjectAction';
import { CameraFlyToAction } from '../actions/CameraFlyToAction';
import { ObjectSetColorAction } from '../actions/ObjectSetColorAction';
import { StateManager } from '../managers/StateManager';
import { ColliderManager } from '../managers/ColliderManager';
import { LevelManager } from '../managers/LevelManager';
import { RelationshipManager } from '../managers/RelationshipManager';
import { Component } from '../components/Component';
import { AppTimerComponent } from '../components/AppTimerComponent';
import { AppHelperComponent } from '../components/AppHelperComponent';
import { AppResourcePoolComponent } from '../components/AppResourcePoolComponent';
import { BaseComponentGroup } from '../components/BaseComponentGroup';
import { AppRenderConfigComponent } from '../components/AppRenderConfigComponent';
import { Picker } from './Picker';
import { Global } from './Global';
import { Scene } from './Scene';
import { Logo } from './Logo';
import { EventType, ActionType } from '../const';
import { ThemeManager } from '../managers/ThemeManager';
import { AppPluginComponent } from '../components/AppPluginComponent';
import { AppBundleComponent } from '../components/AppBundleComponent';
import { MonitorManagerComponent } from '../components/MonitorManagerComponent';

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

const State = {
	Waiting: 0,
	Running: 1,
	Stopped: 2
};

const Flag = {
	Disposing: 1 << 0,
	Disposed: 1 << 1
};

const DummyEventManager = {
	dispatchEvent: function () { },
};

const DumnyFunction = function () { }

const registerComponentParam = { isResident: true };

const _authDataEvent = StringEncoder.toText("<@secret __!!a$u^t@h*event@__>");
const _askAuthDataEvent = StringEncoder.toText("<@secret __a)s*k&!!a$u^t@h*event@__>");

/**
 * @class App
 * The main application.
 * @memberof THING
 * @extends THING.BaseComponentGroup
 * @public
 */
class App extends BaseComponentGroup {

	#logo;

	static instances = [];

	static completeCallbacks = [];

	/**
	 * The current application.
	 * @type {THING.App}
	 * @example
	 * let app = THING.App.current;
	 * let ret = app.picker != null;
	 * // @expect(ret == true);
	 */
	static get current() {
		return Utils.getCurrentApp();
	}

	static set current(app) {
		Utils.setCurrentApp(app);
	}

	static defaultSettings = {
		envMap: true, // Control default envMap
	};

	/**
	 * Get alive instances.
	 * @returns {Array<THING.App>}
	 * @private
	 */
	static getAliveInstances() {
		return App.instances.filter(app => {
			return !app.isDisposed;
		});
	}

	/**
	 * Add a callback to the end of the app instantiation
	 * @param {Function} callback The callback.
	 * @param {Number} priority The priority. (The default is 0, the smaller one is executed first)
	 * @public
	 * @example
	 * THING.App.addCompleteCallback((app)=>{
	 *		console.log(The app instantiation is complete.);
	 * },-10);
	 */
	static addCompleteCallback(callback, priority = 0) {
		App.instances.forEach(app => {
			callback(app);
		});

		App.completeCallbacks.push({ callback, priority });

		App.completeCallbacks.sort((a, b) => {
			return b.priority - a.priority;
		});
	}

	/**
	 * The app options.
	 * @typedef {Object} AppOptions
	 * @property {HTMLElement} container? The dom element to render scene.
	 * @property {Boolean} [isEditor=false] True indicates it's running in editor env.
	 * @property {Number|String|Array<Number>} background? The background color.
	 * @property {String} envMap? The environment map resource url.
	 */

	/**
	 * The application constructor.
	 * @param {AppOptions} param The initial parameters.
	 * @example
	 * // Load bundle scene
	 * var app = new THING.App({
	 * 	 url: './scene-bundle',
	 * 	 onComplete: (e) = {
	 *     console.log(e);
	 *   }
	 * });
	 *
	 * // Load gltf scene
	 * var app = new THING.App({
	 *   url: './gltf/scene.gltf',
	 *   onComplete: (e) => {
	 *     console.log(e);
	 *   }
	 * });
	 */
	constructor(param = {}) {
		super(param);

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

		// Register the first callback for complete
		if (!App.current) {
			App.addCompleteCallback((app) => {
				let options = app.options;
				// Load scene
				if (options.url) {
					app.load(options);
				}
			});
		}

		// Add instance
		App.instances.push(this);

		// Make as current
		this.makeCurrent();

		// The initial options
		_private.options = param;

		// Initialize global
		_private.global = new Global(param);

		// Initialize application delegate
		this.#initAppDelegate(param);
		this.#initAppView(param);

		// The default pixel ratio is base on application delegate attributes
		let devicePixelRatio = _private.delegate.getAttribute('DevicePixelRatio');
		if (devicePixelRatio) {
			_private.view.setPixelRatio(devicePixelRatio);
		}

		// Initialize common
		_private.uuid = MathUtils.generateUUID();
		_private.size = [0, 0];
		_private.state = State.Waiting;
		_private.flags = new Flags();

		// Picker
		_private.picker = null;

		// Create managers
		_private.eventManager = new EventManager();
		_private.objectManager = new ObjectManager();
		_private.tweenManager = new TweenManager();
		_private.colliderManager = new ColliderManager();
		_private.actionManager = new ActionManager();
		_private.stateManager = new StateManager();
		_private.levelManager = null;
		_private.resourceManager = null;
		_private.relationshipManager = new RelationshipManager();
		_private.themeManager = new ThemeManager();

		// Core
		_private.scene = null;
		_private.root = null;

		// Callbacks
		_private.beforeDisposeCallbacks = new Callbacks();
		_private.afterDisposeCallbacks = new Callbacks();
		_private.beforeTickCallbacks = new Callbacks();
		_private.afterTickCallbacks = new Callbacks();

		// Create scene
		let scene = Utils.createObject('Scene', { name: 'RootScene' });

		// Initialize core
		this.#initSelector(param);
		this.#initResourceManager(param['resourceManager']);
		this.#initScene(scene, param);
		this.#initPicker();
		this.#initCamera(param['camera']);
		this.#initComponents();
		this.#initEvents();
		this.#initLevelManager();

		// register actions
		this.#registerAction();

		// Initialize delegate
		_private.delegate.init();

		// Start to run
		this.run();

		// Get the initial size
		let size = [];
		_private.delegate.getSize(size);

		// Resize it
		this.onResize(size[0], size[1]);

		// Notify resize event when start up
		this.trigger('Resize', {
			width: size[0],
			height: size[1]
		});

		this.#setupScene();

		// Create logo
		this.#logo = new Logo();
		const accessors = _private.scene.getAttribute(StringEncoder.toText("<@secret Accessors>"));
		this.#logo.init(accessors[StringEncoder.toText("<@secret overlayLogoRootNode>")]);

		// Notify outside the application has been completed
		Utils.onCompleteApp(this);

		// Notify complete callbacks
		App.completeCallbacks.forEach(info => {
			info.callback(this);
		});

		if (_NEED_LOGGER) {
			let msg = { "renderCapabilities": this.renderCapabilities }
			Utils.logger.api("App_create", { msg: JSON.stringify(msg) })
		}
	}

	// #region Private

	#initAppDelegate(param) {
		let _private = this[__.private];

		let delegate = Utils.createObject('AppDelegate', param);
		if (!delegate) {
			Utils.error(`Create app delegate failed`);
		}

		_private.delegate = delegate;
	}

	#initAppView(param) {
		let _private = this[__.private];

		let canvas = Utils.parseValue(param['canvas'], null);

		_private.view = Utils.createObject('AppView', { canvas, appDelegate: this.delegate, compatibleOptions: param.compatibleOptions || {} });
		_private.renderCapabilities = _private.view.getCapabilities();

		if (_DEBUG) {
			let rendererType = _private.view.getAttribute('RendererType');
			let disableBackward = _private.delegate.getAttribute('DisableBackward');

			Utils.log(`Init app view(rendererType: ${rendererType}, disableBackward: ${disableBackward})`);
		}
	}

	#initSelector(param) {
		Selector.init(param['selector']);
	}

	#initScene(scene, param) {
		let _private = this[__.private];

		_private.scene = new Scene({
			scene,
			initLights: param['initLights'],
			envMap: param['envMap'] || param['env'],
			defaultSettings: App.defaultSettings,
		});

		_private.root = _private.scene.rootObjects['scene'];
	}

	#initResourceManager(options = {}) {
		let _private = this[__.private];

		_private.resourceManager = new ResourceManager(options);

		// Load resources for global cache
		_private.global.loadResource(_private.resourceManager);
	}

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

		_private.picker = new Picker();
	}

	#initCamera(options = {}) {
		let _private = this[__.private];

		// We set initial position to prevent target and position is the same
		let cameraOptions = Object.assign({}, options);
		cameraOptions['position'] = [10, 10, 10];

		let camera = new Camera(cameraOptions);
		camera.enableViewport = true;
		camera.background = Utils.parseValue(cameraOptions['background'], [0.5647058823529412, 0.5647058823529412, 0.5647058823529412]);

		// Add camera into scene
		_private.root.add(camera);

		// Make as main camera
		let scene = _private.scene;
		scene.camera = camera;
		scene.renderCamera = camera;
	}

	#initComponents() {
		this.addComponent(AppTimerComponent, 'timer', registerComponentParam);
		this.addComponent(AppHelperComponent, 'helper', registerComponentParam);
		this.addComponent(AppResourcePoolComponent, 'resourcePool', registerComponentParam);
		this.addComponent(AppRenderConfigComponent, 'renderConfig', registerComponentParam);
		this.addComponent(AppPluginComponent, 'plugin', registerComponentParam);
		this.addComponent(AppBundleComponent, 'bundle', registerComponentParam);
		this.addComponent(MonitorManagerComponent, 'monitorManager', registerComponentParam);
	}

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

		let root = _private.root;

		// Hook focus in
		root.on('windowfocusin', (ev) => {
			root.trigger(EventType.FocusIn, ev);
		}, {
			enumerable: false
		});

		// Hook focus out
		root.on('windowfocusout', (ev) => {
			root.trigger(EventType.FocusOut, ev);
		}, {
			enumerable: false
		});

		// Hook resize event
		root.on('windowresize', (ev) => {
			this.onResize(ev.width, ev.height);
		}, {
			enumerable: false
		});

		// Hook drop files
		root.on('windowdrop', (ev) => {
			root.trigger(EventType.DropFiles, ev);
		}, {
			enumerable: false
		});

		// Hook auth event
		root.on(_askAuthDataEvent, (ev) => {
			let authData = _getAuthData();
			if (authData) {
				root.trigger(_authDataEvent, authData);
			}
		}, {
			enumerable: false
		});
	}

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

		_private.levelManager = new LevelManager();
	}

	#setupScene() {
		const _private = this[__.private];

		if (!_private.view.getAttribute('CompatibleRender')) {
			_private.scene.mainLight.adapter.bindCamera(_private.scene.camera);
		}
	}

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

		_private.actionManager.registerAction(ActionType.CreateObject, new CreateObjectAction());
		_private.actionManager.registerAction(ActionType.CameraFlyTo, new CameraFlyToAction());
		_private.actionManager.registerAction(ActionType.ObjectSetColor, new ObjectSetColorAction());
	}

	// #endregion

	// #region Context

	/**
	 * Make application as current.
	 * @private
	 */
	makeCurrent() {
		Utils.setCurrentApp(this);
	}

	// #endregion

	// #region Common

	/**
	 * When focus change.
	 * @callback onFocusChangeCallback
	 * @param {FocusEvent} ev The event info.
	 */

	/**
	 * Get/Set onFocusChange callback
	 * @type {onFocusChangeCallback}
	 * @private
	 */
	get onFocusChange() {
		let _private = this[__.private];
		_private.delegate.getFocusChangeCallback();
	}
	set onFocusChange(value) {
		let _private = this[__.private];
		_private.delegate.setFocusChangeCallback(value);
	}

	/**
	 * Set foucs on it.
	 * @private
	 */
	focus() {
		let _private = this[__.private];

		_private.delegate.focus();
	}

	/**
	 * Dispose.
	 * @public
	 */
	dispose() {
		let _private = this[__.private];

		if (_private.flags.has(Flag.Disposed)) {
			return;
		}

		this.trigger(EventType.AppQuit);

		_private.beforeDisposeCallbacks.invoke();
		{
			_private.flags.enable(Flag.Disposing, true);

			this.stop();

			_private.levelManager.dispose();
			_private.levelManager = null;

			this.removeAllComponents(true);

			_private.scene.dispose();
			_private.scene = null;
			_private.root = null;

			_private.picker.dispose();
			_private.picker = null;

			_private.eventManager.dispose();
			_private.eventManager = DummyEventManager; // We do not set to null due to keep code clear

			_private.colliderManager.dispose();
			_private.colliderManager = null;

			_private.actionManager.dispose();
			_private.actionManager = null;

			_private.stateManager.dispose();
			_private.stateManager = null;

			_private.tweenManager.dispose();
			_private.tweenManager = null;

			_private.objectManager.dispose();
			_private.objectManager = null;

			_private.resourceManager.dispose();
			_private.resourceManager = null;

			_private.relationshipManager.dispose();
			_private.relationshipManager = null;

			_private.global.dispose();
			_private.global = null;

			_private.view.dispose();
			_private.view = null;

			_private.delegate.dispose();
			_private.delegate = null;

			_private.beforeTickCallbacks.clear();
			_private.afterTickCallbacks.clear();

			_private.flags.enable(Flag.Disposing, false);
		}
		_private.afterDisposeCallbacks.invoke();

		// Dispose complete
		_private.flags.enable(Flag.Disposed, true);

		// Update current app if it's needed
		if (App.current == this) {
			let instances = App.instances;
			Utils.removeFromArray(instances, this);

			if (instances.length) {
				App.current = instances[0];
				App.current.makeCurrent();
			}
			else {
				App.current = null;
			}
		}

		// Make some interfaces as dummy function for easy coding
		this.trigger = DumnyFunction;

		if (_NEED_LOGGER) {
			let msg = { "renderCapabilities": this.renderCapabilities };
			Utils.logger.api("App_dispose", { msg: JSON.stringify(msg) })
		}
	}

	/**
	 * Create Object
	 * @param {Object} param The parameter list
	 * @param {String} param.type Object type
	 * @param {Object} param.options? Object create parameters
	 * @return {THING.BaseObject}
	 * @example
	 * let app =  THING.App.current;
	 * let box = app.create({
	 *   type: 'Box',
	 *   name: 'box',
	 *   position: [1, 1, 1],
	 *   onComplete: function() {
	 *   	console.log('box01 created!');
	 *   }
	 * });
	 * let ret = box instanceof THING.Box;
	 * // @expect(ret == true);
	 * @public
	 */
	create(param) {
		if (!param) {
			console.warn('create object failed, due to param is null');
			return null;
		}

		const typeClass = Utils.getRegisteredClasses()[param['type']];

		if (!typeClass) {
			console.warn('Object creation failed, due to type not found or not supported');
			return null;
		}

		delete param['type'];

		if (param['options']) {
			return new typeClass(param.options);
		}

		return new typeClass(param);
	}

	/**
	 * Load scene file
	 * @param {String|Object} url - The load URL or options.
	 * @param {Object} options - The load options.
	 * @param {String} options.url - The resource URL.
	 * @param {Boolean} options.dynamic - Dynamic loading scene.
	 * @param {Boolean} options.hidden - Hidden loading.
	 * @param {Boolean} options.useDefaultTheme - Use defalt theme for scene. (The last registered theme is used by default)
	 * @param {Boolean} options.useDefaultViewpoint - Use default viewpoint for scene file.
	 * @param {Boolean} options.useDefaultRenderSettings - Use default render settings for scene file.
	 * @param {Array} options.position - Set the scene position.
	 * @param {Array} options.rotation - Set the scene rotation.
	 * @param {Function} options.onComplete - The load complete callback function.
	 * @param {Function} options.onProgress - The progress callback function.
	 * @param {Function} options.onError - The error callback function.
	 * @returns {Promise} - A Promise that resolves when the loading is complete.
	 * @example
	 * THING.App.current.load('./assets/scenes/scene.gltf').then((ev) => {
	 *     let objs = ev.objects;
	 * 	   let count =  objs.length;
	 *    // @expect(count > 0);
	 * })
	 * @public
	 */
	load(url, options) {
		return this.resourcePool.load(url, options);
	}

	/**
	 * Load plugin
	 * @param {String|Object} url - The load URL or options.
	 * @param {Object} options - The load options.
	 * @param {String} options.url The plugin URL.
	 * @param {String} options.name The plugin name.
	 * @param {Function} options.onComplete - The load complete callback function.
	 * @param {Function} options.onError - The error callback function.
	 * @return {Promise}
	 * @example
	 * THING.App.current.loadPlugin('./assets/plugins/plugin.json').then((plugin)=>{
	 *	console.log(plugin);
	 * });
	 * @public
	 */
	loadPlugin(url, options) {
		return this.resourcePool.loadPlugin(url, options);
	}

	/**
	 * Load image texture from URL.
	 * @param {String} url The resource url.
	 * @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
	 * @returns {THING.ImageTexture}
	 * @example
	 * 	let image = THING.App.current.loadImageTexture('./flower.png');
	 * 	await image.waitForComplete();
	 * 	console.log(image);
	 * @public
	 */
	loadImageTexture(url, sampler) {
		return this.resourcePool.loadImageTexture(url, sampler);
	}

	/**
	 * Uninstall plugin by name.
	 * @param {String} name The plugin name.
	 * @returns {Boolean}
	 * @example
	 * 	THING.App.current.uninstall(plugin.name);
	 * @public
	 */
	uninstall(name) {
		return this.plugin.uninstall(name);
	}

	/**
	 * Load from URL or data.
	 * @param {Object} options The options.
	 * @param {String|Array<String>} url The resource URL(s).
	 * @param {Object} data The json data.
	 * @example
	 * app.load({ url: './blueprints/myBP.json' });
	 * app.run();
	 * @private
	 */
	loadBlueprint(options = {}) {
		this.blueprint.load(options);
		this.blueprint.run();
	}

	/**
	 * Check whether it's disposing.
	 * @type {Boolean}
	 * @private
	 */
	get isDisposing() {
		let _private = this[__.private];

		return _private.flags.has(Flag.Disposing);
	}

	/**
	 * Check whether it had been disposed.
	 * @type {Boolean}
	 * @private
	 */
	get isDisposed() {
		let _private = this[__.private];

		return _private.flags.has(Flag.Disposed);
	}

	/**
	 * Get the options.
	 * @type {AppOptions}
	 * @private
	 */
	get options() {
		let _private = this[__.private];

		return _private.options;
	}

	/**
	 * Get the global.
	 * @type {THING.Global}
	 * @private
	 */
	get global() {
		return this[__.private].global;
	}

	/**
	 * Get the uuid.
	 * @type {String}
	 * @private
	 */
	get uuid() {
		return this[__.private].uuid;
	}

	/**
	 * Get the system info.
	 * @type {Object}
	 * @private
	 */
	get systemInfo() {
		let _private = this[__.private];

		let extensions = _private.view.getExtensions();

		return _private.delegate.getAttribute('System', {
			context: _private.view.getContext(),
			extensions,
		});
	}

	/**
	 * Get the picker.
	 * @type {THING.Picker}
	 * @example
	 * let picker = app.picker;
	 * // @expect(picker != null)
	 * picker.enable = false;
	 * // @expect(picker.enable == false)
	 * @public
	 */
	get picker() {
		let _private = this[__.private];

		return _private.picker;
	}

	// #endregion

	// #region Components

	addComponent(component, name, args) {
		if (component.className == Component.className) {
			Utils.error(`Add component (name: '${name}') to app failed`);
			return false;
		}

		return super.addComponent(component, name, args);
	}

	/**
	 * Get the blueprint component.
	 * @type {THING.BlueprintComponent}
	 * @private
	 */
	get blueprint() {
		return this.root.blueprint;
	}

	/**
	 * Get the dynamic load component.
	 * @type {THING.DynamicLoadComponent}
	 * @private
	 */
	get dynamicLoad() {
		return this.root.dynamicLoad;
	}

	// #endregion

	// #region Manager

	/**
	 * Get the object manager.
	 * @type {THING.ObjectManager}
	 * @private
	 */
	get objectManager() {
		return this[__.private].objectManager;
	}

	/**
	 * Get the event manager.
	 * @type {THING.EventManager}
	 * @private
	 */
	get eventManager() {
		return this[__.private].eventManager;
	}

	/**
	 * Get the resource manager.
	 * @type {THING.ResourceManager}
	 * @private
	 */
	get resourceManager() {
		return this[__.private].resourceManager;
	}

	/**
	 * Get the tween manager.
	 * @type {THING.TweenManager}
	 * @private
	 */
	get tweenManager() {
		return this[__.private].tweenManager;
	}

	/**
	 * Get the tween manager.
	 * @type {THING.TweenManager}
	 * @public
	 */
	get tween() {
		return this[__.private].tweenManager;
	}

	/**
	 * Get the collider manager.
	 * @type {THING.ColliderManager}
	 * @private
	 */
	get colliderManager() {
		return this[__.private].colliderManager;
	}

	/**
	 * Get the action manager.
	 * @type {THING.ActionManager}
	 * @private
	 */
	get actionManager() {
		return this[__.private].actionManager;
	}

	/**
	 * Get the state manager.
	 * @type {THING.StateManager}
	 * @private
	 */
	get stateManager() {
		return this[__.private].stateManager;
	}

	/**
	 * Get the level manager.
	 * @type {THING.LevelManager}
	 * @example
	 * let app = THING.App.current;
	 * let level = app.level;
	 * let target = app.query('.Entity')[0];
	 * level.change(target, {
	 *   onComplete: function(){
	 * 		let ret = level.current == target;
	 * 		// @expect(ret == true);
	 *   }
	 * });
	 * @public
	 */
	get level() {
		return this[__.private].levelManager;
	}

	/**
	 * Get the level manager.
	 * @type {THING.LevelManager}
	 * @example
	 * let app = THING.App.current;
	 * let level = app.level;
	 * let levelManager = app.levelManager;
	 * let ret = level == levelManager;
	 * // @expect(ret == true);
	 * @deprecated 2.7
	 * @private
	 */
	get levelManager() {
		return this.level;
	}

	/**
	 * Get the relationship manager.
	 * @type {THING.RelationshipManager}
	 * @private
	 */
	get relationshipManager() {
		return this[__.private].relationshipManager;
	}

	/**
	 * Get the theme manager.
	 * @type {THING.ThemeManager}
	 * @private
	 */
	get themeManager() {
		return this[__.private].themeManager;
	}

	// #endregion

	// #region Render

	/**
	 * Add callback before dispose.
	 * @param {Function} callback The function.
	 * @private
	 */
	addBeforeDisposeCallback(callback) {
		this[__.private].beforeDisposeCallbacks.add(callback);
	}

	/**
	 * Remove callback before dispose.
	 * @param {Function} callback The function.
	 * @private
	 */
	removeBeforeDisposeCallback(callback) {
		this[__.private].beforeDisposeCallbacks.remove(callback);
	}

	/**
	 * Add callback after dispose.
	 * @param {Function} callback The function.
	 * @private
	 */
	addAfterDisposeCallback(callback) {
		this[__.private].afterDisposeCallbacks.add(callback);
	}

	/**
	 * Remove callback after dispose.
	 * @param {Function} callback The function.
	 * @private
	 */
	removeAfterDisposeCallback(callback) {
		this[__.private].afterDisposeCallbacks.remvoe(callback);
	}

	/**
	 * Add callback before tick.
	 * @param {Function} callback The function.
	 * @private
	 */
	addBeforeTickCallback(callback) {
		this[__.private].beforeTickCallbacks.add(callback);
	}

	/**
	 * Remove callback before tick.
	 * @param {Function} callback The function.
	 * @private
	 */
	removeBeforeTickCallback(callback) {
		this[__.private].beforeTickCallbacks.remove(callback);
	}

	/**
	 * Add callback after tick.
	 * @param {Function} callback The function.
	 * @private
	 */
	addAfterTickCallback(callback) {
		this[__.private].afterTickCallbacks.add(callback);
	}

	/**
	 * Remove callback after tick.
	 * @param {Function} callback The function.
	 * @private
	 */
	removeAfterTickCallback(callback) {
		this[__.private].afterTickCallbacks.remove(callback);
	}

	/**
	 * Add callback before render.
	 * @param {Function} callback The function.
	 * @private
	 */
	addBeforeRenderCallback(callback) {
		this[__.private].scene.addBeforeRenderCallback(callback);
	}

	/**
	 * Remove callback before render.
	 * @param {Function} callback The function.
	 * @private
	 */
	removeBeforeRenderCallback(callback) {
		this[__.private].scene.removeBeforeRenderCallback(callback);
	}

	/**
	 * Add callback after render.
	 * @param {Function} callback The function.
	 * @private
	 */
	addAfterRenderCallback(callback) {
		this[__.private].scene.addAfterRenderCallback(callback);
	}

	/**
	 * Remove callback after render.
	 * @param {Function} callback The function.
	 * @private
	 */
	removeAfterRenderCallback(callback) {
		this[__.private].scene.removeAfterRenderCallback(callback);
	}

	/**
	 * Convert pixel to screen coordinates in [-1, 1].
	 * @param {Array<Number>} position The position.
	 * @returns {Array<Number>}
	 * @private
	 */
	pixelToScreenCoordinate(position) {
		return MathUtils.pixelToScreenCoordinate(position, this.size);
	}

	/**
	 * Convert screen coordinates in [-1, 1] to pixel.
	 * @param {Array<Number>} position The position in [-1, 1].
	 * @returns {Array<Number>}
	 * @private
	 */
	screenCoordinateToPixel(position) {
		return MathUtils.screenCoordinateToPixel(position, this.size);
	}

	/**
	 * Start to run and render.
	 * @private
	 */
	run() {
		let _private = this[__.private];

		if (_private.state == State.Running) {
			return;
		}

		_private.state = State.Running;

		// Get internal variables
		let timer = this.timer;

		// Start to render, elapsedTime is in milliseconds
		timer.init({
			frameRate: _private.options['frameRate'],
			onTick: (deltaTime) => {
				// The delta time in seconds

				// Before tick
				_private.beforeTickCallbacks.invoke(deltaTime);

				// Update core objects
				this.#logo.update(deltaTime);

				// Update managers
				_private.objectManager.update(deltaTime);
				_private.tweenManager.update(deltaTime);
				_private.colliderManager.update(deltaTime);
				_private.stateManager.update(deltaTime);
				_private.eventManager.update(deltaTime);

				// After tick
				_private.objectManager.lateUpdate(deltaTime);
				_private.afterTickCallbacks.invoke(deltaTime);

				// Render scene
				_private.scene.render(deltaTime);
			}
		});

		// Start timer with delegate
		_private.delegate.run((elapsedTime, frame) => {
			this.timer.update(elapsedTime);
		});
	}

	/**
	 * Stop to run and render.
	 * @private
	 */
	stop() {
		let _private = this[__.private];

		if (_private.state == State.Stopped) {
			return;
		}

		_private.state = State.Stopped;

		_private.delegate.stop();
	}

	/**
	 * Lock size to ignore resize event.
	 * @param {Number} width The width in pixel.
	 * @param {Number} height The height in pixel.
	 * @returns {Boolean}
	 * @private
	 */
	lockSize(width, height) {
		let _private = this[__.private];

		if (!_private.delegate.lockSize(width, height)) {
			return false;
		}

		this.onResize(width, height);

		return true;
	}

	/**
	 * Unlock size.
	 * @private
	 */
	unlockSize() {
		let _private = this[__.private];

		_private.delegate.unlockSize();

		let size = [0, 0];
		_private.delegate.getSize(size);

		this.onResize(size[0], size[1]);
	}

	/**
	 * Capture screen shot into pixel buffer in RGBA color format.
	 * @param {Number} width The image width in pixel, if it not provide then use the current width.
	 * @param {Number} height The image height in pixel, if it not provide then use the current height.
	 * @returns {Uint8Array}
	 * @example
	 * 	let data = THING.App.current.captureScreenshotToData(640, 480);
	 *  let ret = data.length == (640 * 480 * 4);
	 *  // @expect(ret == true);
	 */
	captureScreenshotToData(width, height) {
		return this.camera.captureToData(width, height);
	}

	/**
	 * Capture screen shot into image.
	 * @param {Number} width The image width in pixel.
	 * @param {Number} height The image height in pixel.
	 * @returns {Object}
	 * @example
	 * 	let image = THING.App.current.captureScreenshotToImage(640, 480);
	 * 	let ret1 = image instanceof Image;
	 *  let ret2 = image.width == 640 && image.height == 480;
	 *  THING.Utils.setTimeout(() => {
	 *	// @expect(ret1 == true && ret2 == true);
	 *	}, 100);
	 */
	captureScreenshotToImage(width, height) {
		return this.camera.captureToImage(width, height);
	}

	/**
	 * Capture screen shot into file.
	 * @param {String} fileName The file name.
	 * @param {Number} width The image width in pixel.
	 * @param {Number} height The image height in pixel.
	 * @example
	 * 	THING.App.current.captureScreenshotToFile('cameraCapture', 640, 480);
	 */
	captureScreenshotToFile(fileName, width, height) {
		this.camera.captureToFile(fileName, width, height);
	}

	/**
	 * Get the delegate.
	 * @type {Object}
	 * @private
	 */
	get delegate() {
		let _private = this[__.private];

		return _private.delegate;
	}

	/**
	 * Get the view.
	 * @type {Object}
	 * @private
	 */
	get view() {
		let _private = this[__.private];

		return _private.view;
	}

	/**
	 * Get the container.
	 * @type {Object}
	 * @example
	 * //the html div id is div3d
	 * let id = THING.App.current.container.id;
	 * let ret = id == 'div3d';
	 * // @expect(ret == true);
	 */
	get container() {
		let _private = this[__.private];

		return _private.delegate.getAttribute('Container');
	}

	/**
	 * Get the cursor position in container.
	 * @type {Array<Number>}
	 * @private
	 */
	get cursorPosition() {
		let _private = this[__.private];

		return _private.delegate.getAttribute('CursorPosition');
	}

	/**
	 * Get the size.
	 * @type {Array<Number>}
	 * @example
	 * //the div3d size is 1024*768
	 * let size = THING.App.current.size;
	 * 	 // @expect(size[0] == 1024 && size[1] == 768);
	 */
	get size() {
		let _private = this[__.private];

		return _private.size.slice(0);
	}

	/**
	 * Get the scene.
	 * @type {THING.Scene}
	 * @private
	 */
	get scene() {
		let _private = this[__.private];

		return _private.scene;
	}

	/**
	 * Get the root.
	 * @type {THING.RootObject}
	 * @example
	 * 	let root = THING.App.current.root;
	 *  // @expect(root.isRootObject == true)
	 */
	get root() {
		let _private = this[__.private];

		return _private.root;
	}

	/**
	 * get all relationships
	 * @type {Array<THING.Relationship>}
	 * @public
	 * @readonly
	 * @example
	 *  let app = THING.App.current;
	 *  let count1 = app.relationships.length;
	 *  app.relationshipManager.addRelationship({
	 * 		source: app.root,
	 * 	    target: app.root
	 *  })
	 *  let count2 = app.relationships.length;
	 *  let ret = count2 - count1;
	 *  // @expect(ret == 1)
	 */
	get relationships() {
		let _private = this[__.private];

		return _private.relationshipManager.relationships;
	}

	/**
	 * Get/Set the camera.
	 * @type {THING.Camera}
	 * @example
	 * let camera = new THING.Camera();
	 * camera.far = 100;
	 * THING.App.current.camera = camera;
	 * let ret = THING.App.current.camera.far == 100;
	 * // @expect(ret == true)
	 */
	get camera() {
		return this[__.private].scene.camera;
	}
	set camera(value) {
		this[__.private].scene.camera = value;
	}

	/**
	 * Get/Set the render camera.
	 * @type {THING.Camera}
	 * @private
	 */
	get renderCamera() {
		return this[__.private].scene.renderCamera;
	}
	set renderCamera(value) {
		this[__.private].scene.renderCamera = value;
	}

	/**
	 * Get the render capabilities.
	 * @type {RendererCapabilities}
	 * @private
	 */
	get renderCapabilities() {
		return this[__.private].renderCapabilities;
	}

	/**
	 * Get/Set pixel ratio.
	 * Set/Get Pixel Ratio The default is 1, and it can be set to a value between 0-1.
	 * The larger the value, the clearer the rendering effect (lower frame rate),
	 * The smaller the value, the blurrier the rendering (increased frame rate)
	 * On mobile devices, in order to render the frame rate, app.pixelRatio can be set to a value less than 1.
	 * @type {Number}
	 */
	get pixelRatio() {
		let _private = this[__.private];

		return _private.view.getPixelRatio();
	}
	set pixelRatio(value) {
		let _private = this[__.private];

		_private.view.setPixelRatio(value);
	}

	// #endregion

	// #region logger

	_outputLogger(funcName, condition) {
		let msg = { "condition": condition };
		Utils.logger.api("App_" + funcName, { msg: JSON.stringify(msg) });
	}

	// #endregion

	// #region Query

	/**
	 * Find children by conditions and returns the first one.
	 * @param {String} condition The conditions.
	 * @returns {THING.BaseObject}
	 * @example
	 * 	let obj = THING.App.current.find('.BaseObject');
	 *  let ret = obj instanceof THING.BaseObject;
	 * // @expect(ret == true)
	 *  let cylinder = THING.App.current.find('.Cylinder');
	 *  ret = cylinder == null;
	 * // @expect(ret == true)
	 * @public
	 */
	find(condition) {
		return this.root.find(condition);
	}

	/**
	 * Query children by condition.
	 * @param {String} condition The condition to select objects.
	 * @param {ObjectQueryOptions} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * 	let obj = THING.App.current.query('.BaseObject');
	 *  let ret = obj[0] instanceof THING.BaseObject;
	 * // @expect(ret == true)
	 *  let cylinder = THING.App.current.query('.Cylinder');
	 *  ret = cylinder.length == 0;
	 * // @expect(ret == true)
	 * @public
	 */
	query(condition, options) {
		if (_NEED_LOGGER) {
			this._outputLogger('query', condition);
		}

		return this.root.query(condition, options);
	}

	/**
	 * Query children by name.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * 	let car = THING.App.current.queryByName('car01')
	 * 	let ret = car.length == 0;
	 * 	// @expect(ret == true)
	 *  car = THING.App.current.queryByName('car1')
	 *  ret = car[0].name == 'car1';
	 * // @expect(ret == true)
	 * @public
	 */
	queryByName(condition, options) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryByName', condition);
		}

		return this.root.queryByName(condition, options);
	}

	/**
	 * Query children by id.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * 	let car = THING.App.current.queryById('1')
	 * 	let ret = car.length == 0;
	 *  // @expect(ret == true);
	 *  car = THING.App.current.queryById('car01')
	 *  ret = car[0].id == 'car01';
	 *  // @expect(ret == true)
	 * @public
	 */
	queryById(condition, options) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryById', condition);
		}

		return this.root.queryById(condition, options);
	}

	/**
	 * Query children by tag.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * 	let car = THING.App.current.queryByTags('car');
	 * 	let ret = car.length == 0;
	 *  // @expect(ret == true)
	 * 	let entity = THING.App.current.queryByTags('Entity');
	 * 	ret = entity[0].tags.has('Entity');
	 *  // @expect(ret == true)
	 * @public
	 */
	queryByTags(condition, options) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryByTags', condition);
		}

		return this.root.queryByTags(condition, options);
	}

	/**
	 * Query children by uuid.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * 	let car = app.queryByUUID('1')
	 * 	let ret = car.length == 0;
	 *  // @expect(ret == true);
	 *  car = app.queryByUUID('1605')
	 *  ret = car[0].uuid == '1605';
	 *  // @expect(ret == true)
	 * @public
	 */
	queryByUUID(condition, options) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryByUUID', condition);
		}

		return this.root.queryByUUID(condition, options);
	}

	/**
	 * Query children by type.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * 	let car = THING.App.current.queryByType('car')
	 * 	let ret = car.length == 0;
	 * // @expect(ret == true)
	 *  let obj = THING.App.current.queryByType('Entity')
	 *  ret = obj[0].type == 'Entity';
	 * // @expect(ret == true)
	 * @public
	 */
	queryByType(condition, options) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryByType', condition);
		}

		return this.root.queryByType(condition, options);
	}

	/**
	 * Query children by userData.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * 	let car = THING.App.current.queryByUserData('test=1');
	 * 	let ret = car.length == 0;
	 *  // @expect(ret == true)
	 *  car = THING.App.current.queryByUserData('id=666');
	 *  ret = car[0].userData.id == 666;
	 * // @expect(ret == true)
	 * @public
	 */
	queryByUserData(condition, options) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryByUserData', condition);
		}

		return this.root.queryByUserData(condition, options);
	}

	/**
	 * Query children by userData.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * 	let car = THING.App.current.queryByData('test=1');
	 * 	let ret = car.length == 0;
	 *  // @expect(ret == true)
	 *  car = THING.App.current.queryByData('id=666');
	 *  ret = car[0].userData.id == 666;
	 * // @expect(ret == true)
	 * @deprecated 2.7
	 * @private
	 */
	queryByData(condition, options) {
		return this.queryByUserData(condition, options);
	}

	/**
	 * Query children by reg exp.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * 	let car = THING.App.current.queryByRegExp(/car/);
	 * 	let ret = car.length == 4;
	 *  //@expect(ret == true)
	 * @public
	 */
	queryByRegExp(condition, options) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryByRegExp', condition.toString());
		}

		return this.root.queryByRegExp(condition, options);
	}

	/**
	 * Query children by reg.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {THING.Selector}
	 * @example
	 * 	let car = THING.App.current.queryByRegExp(/car/);
	 * 	let ret = car.length == 4;
	 *  //@expect(ret == true)
	 * @deprecated 2.7
	 * @private
	 */
	queryByReg(condition, options) {
		return this.queryByRegExp(condition, options);
	}

	/**
	 * Query children by condition in async mode.
	 * @param {String} condition The condition to select objects.
	 * @param {Object} options The options.
	 * @returns {Promise<any>}
	 * @private
	 */
	queryAsync(condition, options) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryAsync', condition);
		}

		return this.root.queryAsync(condition, options);
	}

	/**
	 * Query relationships.
	 * @param {Object} options The options.
	 * @param {String} options.type The type of relationship.
	 * @param {String} options.name The name of relationship.
	 * @returns {Array<Relationship>}
	 * @example
	 * let lightSwitch = new THING.Box();
	 * let light = new THING.Box();
	 * let rel = new THING.Relationship({
	 *      type: 'control',
	 *      source: lightSwitch,
	 *      target: light
	 * });
	 * let relationships = app.queryRelationships({type: 'control'});
	 * let ret1 = relationships[0].source == lightSwitch;
	 * let ret2 = relationships[0].target == light;
	 * // @expect(ret1 == true && ret2 == true);
	 * @public
	 */
	queryRelationships(options = {}) {
		if (_NEED_LOGGER) {
			this._outputLogger('queryRelationships', options);
		}

		return this.relationshipManager.queryRelationships(options);
	}

	// #endregion

	// #region Events

	/**
	 * Get events with type.
	 * @param {String} type The event type, null indicates get all events.
	 * @returns {Array<ObjectListenerInfo>}
	 * @private
	 */
	getEvents(type) {
		return this.root.getEvents(type);
	}

	/**
	 * Pause event.
	 * @param {String} type The event type.
	 * @param {String} condition The condition to select objects.
	 * @param {String} tag The event tag.
	 * @returns {ObjectListenerInfo}
	 * @private
	 */
	getEvent(type, condition, tag) {
		return this.root.getEvent(type.toLowerCase(), condition, tag);
	}

	/**
	 * The base event.
	 * @typedef BaseEvent
	 * @property {String} type The case-insensitive name identifying the type of the event.
	 * @property {THING.BaseObject} object The object that triggered the event.
	 * @property {THING.BaseObject} target A reference to the object to which the event was originally dispatched.
	 */

	/**
	 * The MouseEvent interface represents events that occur due to the user interacting with a pointing device (such as a mouse).
	 * @typedef {BaseEvent} MouseEvent
	 * @property {Number} x The X coordinate of the mouse pointer in local (screen) coordinates.
	 * @property {Number} y The Y coordinate of the mouse pointer in local (screen) coordinates.
	 * @property {Number} button The button number that was pressed when the mouse event was fired.
	 * @property {String} buttonType The button type that was pressed when the mouse event was fired.
	 * @property {Boolean} ctrlKey Returns true if the ctrl key was down when the mouse event was fired.
	 * @property {Boolean} shiftKey Returns true if the shift key was down when the mouse event was fired.
	 * @property {Boolean} altKey Returns true if the alt key was down when the mouse event was fired.
	 */

	/**
	 * The KeyboardEvent objects describe a user interaction with the keyboard.
	 * @typedef {BaseEvent} KeyboardEvent
	 * @property {Number} keyCode Returns a number representing the key value of the key represented by the event.
	 * @property {String} code Returns a string representing the key value of the key represented by the event.
	 * @property {Boolean} ctrlKey Returns a boolean value that is true if the Ctrl key was active when the key event was generated.
	 * @property {Boolean} shiftKey Returns a boolean value that is true if the Shift key was active when the key event was generated.
	 * @property {Boolean} altKey  Returns a boolean value that is true if the Alt key was active when the key event was generated.
	 */

	/**
	 * The Event interface represents an event trigger.
	 * @callback EventCallback
	 * @param {MouseEvent|KeyboardEvent} ev The case-insensitive name identifying the type of the event.
	 * @returns {any}
	 */

	/**
	 * Register event.
	 * @param {String} type The event type.
	 * @param {String} condition The condition to select objects.
	 * @param {EventCallback} callback The callback function.
	 * @param {String} tag The event tag.
	 * @param {Number} priority The priority value(default is 0, higher value will be processed first)
	 * @param {Object} options The options.
	 * @param {Boolean} options.useCapture True indicates capture all same events from children.
	 * @example
	 * let app = THING.App.current;
	 * let markOn = 0;
	 * app.on('testOn', function(ev) {
	 * 		markOn = 1;
	 * });
	 * app.trigger('testOn');
	 * // @expect(markOn == 1);
	 * @public
	 */
	on(type, condition, callback, tag, priority, options) {
		this.root.on(type, condition, callback, tag, priority, options);
	}

	/**
	 * Register event what just trigger once time.
	 * @param {String} type The event type.
	 * @param {String} condition The condition to select objects.
	 * @param {EventCallback} callback The callback function.
	 * @param {String} tag The event tag.
	 * @param {Number} priority The priority value(default is 0, higher value will be processed first)
	 * @param {Object} options The options.
	 * @param {Boolean} options.useCapture True indicates capture all same events from children.
	 * @example
	 * let app = THING.App.current;
	 * let markOnce = 0;
	 * app.once('testOnce', function(ev) {
	 * 		markOnce = 1;
	 * });
	 * app.trigger('testOnce');
	 * // @expect(markOnce == 1);
	 * markOnce = 0;
	 * app.trigger('testOnce');
	 * // @expect(markOnce == 0);
	 * @public
	 */
	once(type, condition, callback, tag, priority, options) {
		this.root.once(type, condition, callback, tag, priority, options);
	}

	/**
	 * Unregister event.
	 * @param {String} type The event type.
	 * @param {String} condition The condition to select objects.
	 * @param {String} tag The event tag.
	 * @example
	 * let app = THING.App.current;
	 * let markOff = 0;
	 * app.on('testOff', function(ev) {
	 * 		markOff = 1;
	 * });
	 * app.trigger('testOff');
	 * // @expect(markOff == 1);
	 * markOff = 0;
	 * app.off('testOff');
	 * app.trigger('testOff');
	 * // @expect(markOff == 0);
	 * @public
	 */
	off(type, condition, tag) {
		this.root.off(type, condition, tag);
	}

	/**
	 * Pause event.
	 * @param {String} type The event type.
	 * @param {String} condition The condition to select objects.
	 * @param {String} tag The event tag.
	 * @returns {Boolean}
	 * @example
	 * let app = THING.App.current;
	 * let markPause = 0;
	 * app.on('testPause', function(ev) {
	 * 		markPause = 1;
	 * });
	 * app.trigger('testPause');
	 * // @expect(markPause == 1);
	 * markPause = 0;
	 * app.pauseEvent('testPause');
	 * app.trigger('testPause');
	 * // @expect(markPause == 0);
	 * @public
	 */
	pauseEvent(type, condition, tag) {
		return this.root.pauseEvent(type, condition, tag);
	}

	/**
	 * Resume event.
	 * @param {String} type The event type.
	 * @param {String} condition The condition to select objects.
	 * @param {String} tag The event tag.
	 * @returns {Boolean}
	 * @example
	 * let app = THING.App.current;
	 * let markResume = 0;
	 * app.on('testResume', function(ev) {
	 * 		markResume = 1;
	 * });
	 * app.trigger('testResume');
	 * // @expect(markResume == 1);
	 * markResume = 0;
	 * app.pauseEvent('testResume');
	 * app.trigger('testResume');
	 * // @expect(markResume == 0);
	 * app.resumeEvent('testResume');
	 * app.trigger('testResume');
	 * // @expect(markResume == 1);
	 * @public
	 */
	resumeEvent(type, condition, tag) {
		return this.root.resumeEvent(type, condition, tag);
	}

	/**
	 * Trigger event.
	 * @param {String} type The event type.
	 * @param {Object} ev The event info.
	 * @param {Object} options The options.
	 * @returns {*}
	 * @example
	 *  let markTrigger = {};
	 * 	THING.App.current.on('myEvent', function(ev) {
	 * 		markTrigger = ev;
	 * 	});
	 * 	THING.App.current.trigger('myEvent', { result: true });
	 *  let ret = markTrigger.result;
	 *  // @expect(ret == true);
	 * @public
	 */
	trigger(type, ev, options) {
		return this.root.trigger(type, ev, options);
	}

	// #endregion

	// #region Clear Buffers

	/**
	 * Enable/Disable auto clear color buffer.
	 * @type {Boolean}
	 * @private
	 */
	get autoClearColorBuffer() {
		return this.camera.autoClearColorBuffer;
	}
	set autoClearColorBuffer(value) {
		this.camera.autoClearColorBuffer = value;
	}

	/**
	 * Enable/Disable auto clear depth buffer.
	 * @type {Boolean}
	 * @private
	 */
	get autoClearDepthBuffer() {
		return this.camera.autoClearDepthBuffer;
	}
	set autoClearDepthBuffer(value) {
		this.camera.autoClearDepthBuffer = value;
	}

	/**
	 * Enable/Disable auto clear stencil buffer.
	 * @type {Boolean}
	 * @private
	 */
	get autoClearStencilBuffer() {
		return this.camera.autoClearStencilBuffer;
	}
	set autoClearStencilBuffer(value) {
		this.camera.autoClearStencilBuffer = value;
	}

	// #endregion

	// #region Resources

	/**
	 * Get/Set the background.
	 * @type {Number|String|Array<Number>|THING.ImageTexture|THING.VideoTexture}
	 * @example
	 * THING.App.current.background = 'gray';
	 * let ret = THING.App.current.background instanceof Array;
	 * // @expect(ret == true )
	 * @public
	 */
	get background() {
		return this.camera.background;
	}
	set background(value) {
		this.camera.background = value;
	}

	/**
	 * Get/Set the default environment map of scene.
	 * @type {THING.CubeTexture}
	 * @example
	 * THING.App.current.background = new THING.CubeTexture('./skyboxes/bluesky');
	 * let ret = THING.App.current.background.url == './skyboxes/bluesky';
	 * // @expect(ret == true )
	 * @public
	 */
	get envMap() {
		return this[__.private].scene.envMap;
	}
	set envMap(value) {
		this[__.private].scene.envMap = value;
	}

	// #endregion

	// #region Event

	/**
	 * When resize.
	 * @param {Number} width The width in pixel.
	 * @param {Number} height The height in pixel.
	 * @private
	 */
	onResize(width, height) {
		let _private = this[__.private];

		// Check whether size has changed
		let size = _private.size;
		if (width == size[0] && height == size[1]) {
			return;
		}

		// Update size
		size[0] = width;
		size[1] = height;

		// Resize core and managers
		_private.scene.resize(width, height);
		_private.objectManager.resize(width, height);

		// Reisze view
		_private.view.setSize([width, height]);

		// Notify resize event
		this.trigger('Resize', { width, height });
	}

	// #endregion

	/**
	 * Check class type.
	 * @type {Boolean}
	 * @example
	 * 	if (app.isApp) {
	 * 		console.log(`It's app`);
	 * 	}
	 * @public
	 */
	get isApp() {
		return true;
	}

}

setTimeout(() => {
	Utils._scriptLoader.startAnimationFrame((deltaTime) => {
		// Make delta time to seconds
		deltaTime /= 1000;

		// Update selector
		Selector.update(deltaTime);

		// Update Utils
		Utils.update(deltaTime);
	});
}, 100);

export { App }