import { Flags, ResolvablePromise } from '@uino/base-thing';
import { Utils } from '../common/Utils';
import { BaseComponent } from './BaseComponent';
const Flag = {
Started: 1 << 0,
Enable: 1 << 1,
Removed: 1 << 2,
AsyncCompleted: 1 << 3,
};
/**
* When start to active component(just only once) in async mode.
* @callback OnStartAsyncComponentCallback
* @param {Object} param The initial parameters.
* @returns {Promise<any>}
*/
/**
* When update before render.
* @callback OnLateUpdateComponentCallback
* @param {Number} deltaTime The delta time in seconds.
*/
/**
* @class Component
* The component.
* @memberof THING
* @extends THING.BaseComponent
* @public
*/
class Component extends BaseComponent {
static isInstancedComponent = true;
_flags = new Flags();
/**
* The component that extends some useful interfaces.
* The interface work flow:
* 1. onAwake -> onStart/onStartAsync
* 2. onUpdate -> onLateUpdate
* @example
* class MyRotator extends THING.Component {
* onAwake(param) {
* this.speed = param['speed'];
* }
*
* onStart() {
* this.object.style.color = "0xFF0000";
* }
*
* onUpdate(deltaTime) {
* this.object.rotateY(this.speed * deltaTime);
* }
* }
*
* let box = new THING.Box();
* box.addComponent(MyRotator, 'rotator');
* box.rotator.speed = 100;
*/
constructor(param = {}) {
/**
* When start to active component(just only once) in async mode and wait for finish, it's after onAwake() interface.
* @member {OnStartAsyncComponentCallback} onStartAsync
* @memberof THING.Component
* @instance
*/
/**
* When update before render.
* @member {OnLateUpdateComponentCallback} OnLateUpdate
* @memberof THING.Component
* @instance
*/
super();
this._flags.enable(Flag.Enable, Utils.parseValue(param['enable'], true));
this._isWaitingForUpdate = false;
this._startingPromise = null;
}
// #region Private
_isEditorByObject(object) {
if (object && object.options['isEditor']) {
return true;
}
return false;
}
_isEditor(args) {
if (args && args['isEditor']) {
return true;
}
// Try to get options from self
if (this._isEditorByObject(this.object)) {
return true;
}
// Try to get options from parents object
if (this._belongsToEditor()) {
return true;
}
let isEditor = Utils.parseValue(this.app.options['isEditor'], false);
return isEditor;
}
_initLateUpdate() {
// Prevent object destroy
if (!this.object) {
return;
}
if (this._onUpdate) {
this.onUpdate = this._onUpdate;
this._onUpdate = null;
}
if (!this.onLateUpdate) {
return;
}
this.app.objectManager.addLateUpdateObject(this);
}
_processStart(args) {
if (this._flags.has(Flag.Removed)) {
return;
}
if (this.onStartAsync) {
let startPromise = this.onStartAsync(args);
if (startPromise) {
startPromise.then(() => {
this._isWaitingForUpdate = false;
this._initLateUpdate();
if (this._startingPromise) {
this._startingPromise.resolve();
}
this._flags.enable(Flag.AsyncCompleted, true);
});
}
else {
this._isWaitingForUpdate = false;
this._initLateUpdate();
}
}
else {
this.onStart(args);
this._initLateUpdate();
}
}
_belongsToEditor() {
let isEditor = false;
const parents = this.object.parents;
for (let i = 0; i < parents.length; i++) {
const parent = parents[i];
if (!parent.options) {
continue;
}
if (parent.options.isEditor === true) {
isEditor = true;
break;
}
}
return isEditor;
}
// #endregion
// #region Component - Overrides
onProcessStartEvent(args) {
if (this._isEditor(args)) {
return;
}
if (this._flags.has(Flag.Started)) {
return;
}
this._flags.enable(Flag.Started, true);
if (this.onStartAsync) {
this._isWaitingForUpdate = true;
}
else {
if (this._startingPromise) {
this._startingPromise.resolve();
}
}
// in child component, may not have onUpdate.
this._onUpdate = this.onUpdate;
if (this._onUpdate) {
this.onUpdate = () => {};
}
this.object.waitForComplete().then(() => {
Utils.setTimeout(() => {
this._processStart(args);
});
});
}
onProcessAddEvent(args) {
if (this._isEditor(args)) {
// Remove 'onUpdate' interface when it's running in editor env
this.onUpdate = null;
}
else {
if (this._flags.has(Flag.Enable)) {
args = args || {};
this.onAwake(args);
this.onProcessStartEvent(args);
}
else {
this.active = false;
}
}
}
onProcessActiveChangeEvent(value) {
if (this._isEditor()) {
return;
}
if (value) {
this.onEnable();
this.onProcessStartEvent();
}
else {
this.onDisable();
}
}
onAdd(object, args) {
super.onAdd(object);
// If we add it with prefab object then need to merge options with args
let ownerObject = this.getOwnerObject();
if (ownerObject) {
args = Utils.mergeObject(Object.assign({}, ownerObject.options), args);
}
this.onProcessAddEvent(args);
}
triggerBeforeAddCallback(object) {
if (!this._isEditorByObject(object)) {
super.triggerBeforeAddCallback();
}
}
triggerAfterAddCallback() {
if (!this._isEditor()) {
super.triggerAfterAddCallback();
}
}
onRemove() {
this._flags.enable(Flag.Removed, true);
if (this.onLateUpdate) {
this.app.objectManager.removeLateUpdateObject(this);
}
this.onDestroy();
super.onRemove();
}
triggerBeforeRemoveCallback() {
if (!this._isEditor()) {
super.triggerBeforeRemoveCallback();
}
}
triggerAfterRemoveCallback(object) {
if (!this._isEditorByObject(object)) {
super.triggerAfterRemoveCallback();
}
}
/**
* When add component(just only once).
* @param {Object} args The constructor arguments.
*/
onAwake(args) {
}
/**
* When start to active component(just only once), it's after onAwake() interface.
* @param {Object} args The constructor arguments.
*/
onStart(args) {
}
/**
* When remove component.
*/
onDestroy() {
}
/**
* When active it.
*/
onEnable() {
}
/**
* When deactivate it.
*/
onDisable() {
}
/**
* Import data
* @param {Object} param The import data.
*/
onImport(param) {
}
/**
* Export data
* @returns {Object}
*/
onExport() {
return {
}
}
onActiveChange(value) {
this.onProcessActiveChangeEvent(value);
}
// #endregion
getPrefabRootObject() {
Utils.warn('The interface is outdated, please use it getOwnerObject().');
return this.getOwnerObject();
}
getOwnerObject() {
if (this.object.isOwner) {
return this.object;
}
let owner = null;
const parents = this.object.parents;
for (let i = 0; i < parents.length; i++) {
const parent = parents[i];
if (parent.isOwner === true) {
owner = parent;
break;
}
}
return owner;
}
resolveUrlFromRoot(url) {
let ownerObject = this.getOwnerObject();
if (ownerObject) {
let path = ownerObject.resource.url;
let ext = path._getExtension();
if (ext) {
path = path._getPath();
}
return path._appendPath(url);
}
return url;
}
// #region Accessors
get isWaitingForUpdate() {
return this._isWaitingForUpdate;
}
get camera() {
return this.app.camera;
}
get root() {
return this.app.root;
}
set enable(value) {
this.active = value;
}
get enable() {
return this.active;
}
/**
* Get the starting promise.
* @type {Promise<any>}
*/
get startingPromise() {
if (!this._startingPromise) {
if (this._flags.has(Flag.AsyncCompleted)) {
this._startingPromise = Promise.resolve();
}
else {
if (this.onStartAsync) {
this._startingPromise = new ResolvablePromise();
}
else {
if (this._flags.has(Flag.Started)) {
return Promise.resolve();
}
else {
this._startingPromise = new ResolvablePromise();
}
}
}
}
return this._startingPromise;
}
// #endregion
get isComponent() {
return true;
}
}
export { Component }