import { CancelablePromise } from '@uino/base-thing';
import { Utils } from '../common/Utils';
import { BaseComponent } from './BaseComponent';
import { ActionQueueType } from '../const';
const __ = {
private: Symbol('private'),
}
// #region Private Functions
// Wait for async complete.
function _waitAsyncCallback(resolve, asyncCallback) {
if (_DEBUG) {
const _checkAsyncCallbackInterval = 10 * 1000;
let handler = setInterval(() => {
Utils.error(`Did you forget to invoke resolve() in\n`, asyncCallback);
clearInterval(handler);
}, _checkAsyncCallbackInterval);
let callback = () => {
clearInterval(handler);
resolve();
}
return callback;
}
else {
return resolve;
}
}
// #endregion
/**
* The level event info.
* @typedef {Object} LevelEventInfo
* @property {Array<THING.BaseObject>} path The level path.
* @property {THING.BaseObject} origin The original level object.
* @property {THING.BaseObject} prev The previous level object.
* @property {THING.BaseObject} current The current level object.
* @property {THING.BaseObject} next The next level object (only for leave operation).
* @property {Object} options The level options.
*/
/**
* When leave self level in async mode.
* @callback OnLeaveLevelAsyncCallback
* @param {LevelEventInfo} ev The event info.
* @param {Function} resolve The promise resolve callback function.
* @param {Function} reject The promise reject callback function.
*/
/**
* When leave self level.
* @callback OnLeaveLevelCallback
* @param {LevelEventInfo} ev The event info.
*/
/**
* When enter self level in async mode.
* @callback OnEnterLevelAsyncCallback
* @param {LevelEventInfo} ev The event info.
* @param {Function} resolve The promise resolve callback function.
* @param {Function} reject The promise reject callback function.
*/
/**
* When enter self level.
* @callback OnEnterLevelCallback
* @param {LevelEventInfo} ev The event info.
*/
/**
* When enter self level finished.
* @callback OnFinishedEnterLevelCallback
* @param {LevelEventInfo} ev The event info.
*/
/**
* @class LevelComponent
* The object level component.
* @memberof THING
* @extends THING.BaseComponent
* @public
*/
class LevelComponent extends BaseComponent {
static mustCopyWithInstance = true;
/**
* The level actions of object, it could process object's action(s) when enter its level.
*/
constructor() {
// #region Overrides
/**
* When leave level callback function.
* @member {OnLeaveLevelCallback} onLeave
* @memberof THING.LevelComponent
* @instance
* @private
*/
/**
* When leave level callback function in async mode.
* @member {OnLeaveLevelAsyncCallback} onLeaveAsync
* @memberof THING.LevelComponent
* @instance
* @private
*/
/**
* When enter level callback function.
* @member {OnEnterLevelCallback} onEnter
* @memberof THING.LevelComponent
* @instance
* @private
*/
/**
* When enter level callback function in async mode.
* @member {OnEnterLevelAsyncCallback} onEnterAsync
* @memberof THING.LevelComponent
* @instance
* @private
*/
/**
* When finish level callback function.
* @member {OnFinishedEnterLevelCallback} onFinish
* @memberof THING.LevelComponent
* @instance
* @private
*/
// #endregion
super();
this[__.private] = {};
let _private = this[__.private];
_private.isLeave = false;
// Settings config in the level
_private.config = {
ignoreVisible: false,
ignoreEvent: false,
ignoreStyle: false
}
_private.generateLevelControlParams = (eventParams) => {
let target = null;
if (eventParams.path) {
target = eventParams.path[eventParams.path.length - 1];
}
return {
origin: eventParams.origin,
prev: eventParams.prev,
current: eventParams.current,
next: eventParams.next,
target: target,
path: eventParams.path,
options: eventParams.options
};
}
_private.viewpoint = null;
}
// #region Private
/**
* Leave self from object.
* @param {Object} param The parameters.
* @private
*/
leave(param) {
return new CancelablePromise((resolve, reject, onCancel) => {
onCancel(() => {
});
if (this.onLeaveAsync) {
this.onLeaveAsync.call(this, param, _waitAsyncCallback(resolve, this.onLeaveAsync), reject);
}
else if (this.onLeave) {
this.onLeave.call(this, param);
resolve();
}
else {
resolve();
}
});
}
/**
* Enter self from object.
* @param {Object} param The parameters.
* @private
*/
enter(param) {
let _private = this[__.private];
return new CancelablePromise((resolve, reject, onCancel) => {
onCancel((cancelParam) => {
let levelControls = cancelParam.levelControls;
let controlParams = _private.generateLevelControlParams(cancelParam);
levelControls.forEach(control => {
control.onLeave(controlParams);
});
_private.isLeave = true;
});
if (this.onEnterAsync) {
this.onEnterAsync.call(this, param, _waitAsyncCallback(resolve, this.onEnterAsync), reject);
}
else if (this.onEnter) {
this.onEnter.call(this, param);
resolve();
}
else {
resolve();
}
});
}
/**
* Finish to enter self level.
* @param {Object} param The parameters.
* @private
*/
finish(param) {
if (this.onFinish) {
this.onFinish(param);
}
}
/**
* Get level action by name.
* @param {String} name The action name.
* @returns {ActionProcessor}
* @private
*/
getActionByName(name) {
let actionQueue = this.actionQueue;
if (!actionQueue) {
return null;
}
return actionQueue.getByName(name);
}
/**
* Get level action by type.
* @param {*} type The action type.
* @returns {ActionProcessor}
* @private
*/
getActionByType(type) {
let actionQueue = this.actionQueue;
if (!actionQueue) {
return null;
}
return actionQueue.getByType(type);
}
/**
* Enable/Disable action.
* @param {String} name The action name or type.
* @param {Boolean} value The action enable state.
* @private
*/
enableAction(name, value) {
let actionQueue = this.actionQueue;
if (!actionQueue) {
return;
}
actionQueue.enable(name, value);
}
/**
* Check whether enable action or not.
* @param {String} name The action name or type.
* @returns {Boolean}
* @private
*/
isActionEnabled(name) {
let actionQueue = this.actionQueue;
if (!actionQueue) {
return false;
}
return actionQueue.isEnabled(name);
}
// #endregion
get isCurrentLevel() {
let app = this.object.app;
return this.object == app.levelManager.current;
}
/**
* Get level action queue.
* @type {THING.ActionQueue}
* @private
*/
get actionQueue() {
return this.object.actionGroup.get(ActionQueueType.EnterLevel);
}
/**
* Get level actions.
* @type {Array<ActionProcessor>}
* @private
*/
get actions() {
let actionQueue = this.actionQueue;
if (!actionQueue) {
return [];
}
return actionQueue.actions;
}
/**
* The level configuration object.
* @typedef {Object} LevelConfig
* @property {Boolean} ignoreVisible - Flag indicating whether to ignore the visibility of level setting objects.
* @property {Boolean} ignoreEvent - Flag indicating whether mouse events for level objects are ignored.
* @property {Boolean} ignoreStyle - Flag indicating whether to ignore the style of level setting objects.
*/
/**
* Get or set the level configuration
* @type {LevelConfig}
*
* @example
* // Get the current level configuration
* let levelComponent = THING.App.current.root.level;
* levelComponent.config = { ignoreVisible: true, ignoreStyle: false };
* // @expect { levelComponent.config.ignoreVisible === true }
* // @expect { levelComponent.config.ignoreStyle === false }
* @public
*/
get config() {
let _private = this[__.private];
return _private.config;
}
set config(value) {
let _private = this[__.private];
_private.config = value;
}
/**
* The viewpoint.
* @typedef {Object} Viewpoint
* @property {Array<Number>} position the camera position
* @property {Array<Number>} target the camera target position
*/
/**
* Gets or sets the local camera position and target position (read from the scene file).
* @type {Viewpoint}
*
* @example
* let levelComponent = THING.App.current.root.level;
* // Get the local viewpoint
* let localViewpoint = levelComponent.localViewpoint;
* let ret1 = typeof localViewpoint === 'object';
* let ret2 = localViewpoint === null;
* // @expect { ret1 == true || ret2 == true }
*
* // Set the local viewpoint
* levelComponent.localViewpoint = {
* position: [0, 0, 0],
* target: [1, 1, 1]
* };
* // @expect {levelComponent.localViewpoint.position[0] === 0 && levelComponent.localViewpoint.target[2] === 1}
* @public
*/
get localViewpoint() {
let _private = this[__.private];
return _private.viewpoint;
}
set localViewpoint(value) {
let _private = this[__.private];
_private.viewpoint = value;
}
/**
* Gets or sets the world camera viewpoint position and camera target position (read from the scene file).
* @type {Viewpoint}
*
* @example
* let levelComponent = THING.App.current.root.level;
* // Get the world viewpoint
* let viewpoint = levelComponent.viewpoint;
* let ret1 = typeof viewpoint === 'object';
* let ret2 = viewpoint === null;
* // @expect { ret1 == true || ret2 == true }
*
* // Set the world viewpoint
* levelComponent.viewpoint = {
* position: [0, 0, 0],
* target: [1, 1, 1]
* };
* // @expect {levelComponent.viewpoint.position[0] === 0 && levelComponent.viewpoint.target[2] === 1}
* @public
*/
get viewpoint() {
let _private = this[__.private];
if (!_private.viewpoint) {
return null;
}
return {
target: this.object.selfToWorld(_private.viewpoint.target),
position: this.object.selfToWorld(_private.viewpoint.position)
};
}
set viewpoint(value) {
let _private = this[__.private];
if (value) {
_private.viewpoint = {
target: this.object.worldToSelf(value.target),
position: this.object.worldToSelf(value.position)
};
}
}
/**
* @type {Function}
* @private
*/
async onLeaveAsync(param, resolve, reject) {
let _private = this[__.private];
if (!_private.isLeave) {
let controlParams = _private.generateLevelControlParams(param);
let levelControls = param.levelControls;
for (let i = 0; i < levelControls.length; i++) {
const control = levelControls[i];
await control.onLeave(controlParams);
}
_private.isLeave = true;
}
resolve();
}
/**
* @type {Function}
* @private
*/
async onEnterAsync(param, resolve, reject) {
let _private = this[__.private];
_private.isLeave = false;
let controlParams = _private.generateLevelControlParams(param);
let levelControls = param.levelControls;
for (let i = 0; i < levelControls.length; i++) {
const control = levelControls[i];
if (control) {
await control.onEnter(controlParams);
}
}
resolve();
}
/**
* Imports data from an external source.
* @param {Object} external - The external data to import.
*
* @example
* let levelComponent = THING.App.current.root.level;
* // Import data from an external source
* // Example 1: Importing data
* levelComponent.onImport({ viewpoint: { position: [1, 2, 3], target: [4, 5, 6] }, config: { ignoreStyle:true, ignoreVisible:false } });
* // @expect { levelComponent.localViewpoint.position[0] === 1 }
* // @expect { levelComponent.localViewpoint.target[2] === 6 }
* // @expect { levelComponent.config.ignoreStyle === true }
* // @expect { levelComponent.config.ignoreVisible === false }
* @public
*/
onImport(external) {
if (!external) {
return;
}
let _private = this[__.private];
// Import viewpoint
const viewpoint = external.viewpoint;
if (viewpoint) {
_private.viewpoint = Utils.cloneObject(viewpoint);
}
// Import config
const config = external.config;
if (config) {
if (Utils.isBoolean(config.ignoreEvent)) {
_private.config.ignoreEvent = config.ignoreEvent;
}
if (Utils.isBoolean(config.ignoreStyle)) {
_private.config.ignoreStyle = config.ignoreStyle;
}
if (Utils.isBoolean(config.ignoreVisible)) {
_private.config.ignoreVisible = config.ignoreVisible;
}
}
}
/**
* Export data to an external source.
* @returns {Object|null} - The exported data.
*
* @example
* let levelComponent = THING.App.current.root.level;
* levelComponent.onImport({ viewpoint: { position: [1, 2, 3], target: [4, 5, 6] }, config: { ignoreStyle:true, ignoreVisible:false } });
* // Export data to an external source
* let exportedData = levelComponent.onExport();
* // @expect { exportedData.viewpoint.position[1] === 2 }
* // @expect { exportedData.viewpoint.target[0] === 4 }
* // @expect { exportedData.config.ignoreStyle === true }
* let ret = exportedData.config.ignoreVisible == undefined;
* // @expect { ret === true }
* @public
*/
onExport() {
let _private = this[__.private];
let result = {};
// Export viewpoint
if (_private.viewpoint) {
result.viewpoint = Utils.cloneObject(_private.viewpoint);
}
// False is the default value and is not export
let config = null;
if (_private.config.ignoreEvent === true) {
config = config ? config : {};
config.ignoreEvent = true;
}
if (_private.config.ignoreStyle === true) {
config = config ? config : {};
config.ignoreStyle = true;
}
if (_private.config.ignoreVisible === true) {
config = config ? config : {};
config.ignoreVisible = true;
}
// Export config
if (config) {
result.config = config;
}
return Object.keys(result).length ? result : null;
}
}
export { LevelComponent }