import { Flags, CancelablePromise } from '@uino/base-thing';
import { Utils } from '../common/Utils'
import { EventType } from '../const';
import { LevelControlManager } from './LevelControlManager';
import { BaseComponentGroup } from '../main';
const __ = {
private: Symbol('private'),
}
const Flag = {
Changing: 1 << 0,
}
const cEnterLevelCompleteEventTag = '__LevelManager_enter_level_complete_event';
const cLeaveLevelCompleteEventTag = '__LevelManager_leave_level_complete_event';
const cDestroyObjectEventTag = '__LevelManager_object_destroy_event';
/**
* @class LevelManager
* 层级管理器
* @memberof THING
* @public
* @example
* const manager = app.levelManager;
*/
class LevelManager extends BaseComponentGroup {
/**
* 层级管理器类,用于管理层级切换和过渡
*/
constructor(app) {
super();
this[__.private] = {};
let _private = this[__.private];
_private.enable = true;
_private.pendingPromises = [];
_private.changeOptions = null;
_private.prev = null;
_private.current = null;
_private.leavingObject = null;
_private.enteringObject = null;
_private.flags = new Flags();
_private.onFilterPath = null;
_private.app = app;
this.addComponent(new LevelControlManager(app), 'levelControlManager', { isResident: true });
// Leave current level when it had been destroyed
_private.app.on(EventType.BeforeDestroy, '*', (ev) => {
if (!_private.current) {
return;
}
const target = ev.object;
if (_private.current == target || _private.current.isChildOf(target)) {
this.quit();
}
}, cDestroyObjectEventTag);
}
// #region Private
// Notify event.
_notifyEvent(type, params) {
let _private = this[__.private];
let app = _private.app;
let object = params['current'];
// Trigger global events
app.trigger(type, params);
// Prevent root trigger duplicated event
if (object != app.root) {
object.trigger(type, params);
}
}
_notifyComplete(options, path) {
let _private = this[__.private];
// Clear change options
_private.changeOptions = null;
// Clear pendingPromises
_private.pendingPromises.length = 0;
// Finished change
_private.flags.enable(Flag.Changing, false);
// Build event to notify outside
let ev = {
prev: _private.prev,
current: _private.current,
path
};
let onComplete = Utils.parseValue(options['onComplete'], options['complete']);
if (onComplete) {
onComplete(ev);
}
_private.app.trigger(EventType.CompleteEnterLevel, ev);
_private.current.trigger(EventType.CompleteEnterLevel, ev);
}
_enterObjectLevel(object, options, path, origin) {
let _private = this[__.private];
// Update level
_private.prev = _private.current;
_private.current = object;
if (object.destroyed) {
return CancelablePromise.resolve();
}
const enterEventParam = this._generateEventParam(path, origin, null, options);
// Notify before enter level event
this._notifyEvent(EventType.BeforeEnterLevel, enterEventParam);
_private.enteringObject = object;
_private.changingOptions = enterEventParam;
let enterParam = Object.assign({}, enterEventParam);
enterParam.levelControls = this.getControlsByObject(object);
// Enter level
let enterPromises = [];
const controlPromise = object.level.enter(enterParam);
enterPromises.push(controlPromise);
if (!this.levelControlManager.controlCount) {
const eventPromise = new Promise((resolve) => {
_private.app.once(EventType.ObjectLevelComplete, () => {
resolve();
});
this._notifyEvent(EventType.EnterObjectLevel, enterEventParam);
}, cEnterLevelCompleteEventTag);
enterPromises.push(eventPromise);
}
return new CancelablePromise((resolve, reject, onCancel) => {
let canceled = false;
onCancel(() => {
canceled = true;
});
Promise.all(enterPromises).then(() => {
if (canceled) {
return;
}
// Finished level changed
object.level.finish(enterParam);
_private.enteringObject = null;
// Notify enter level event
this._notifyEvent(EventType.EnterLevel, enterEventParam);
// Notify after enter level event
this._notifyEvent(EventType.AfterEnterLevel, enterEventParam);
// Check whether reach the target object
if (object == path[path.length - 1]) {
this._notifyComplete(options, path);
}
_private.changingOptions = null;
resolve();
}).catch(reject);
});
}
_leaveObjectLevel(object, options, path, origin) {
const _private = this[__.private];
if (!_private.current) {
return CancelablePromise.resolve();
}
if (_private.current.destroyed) {
return CancelablePromise.resolve();
}
if (_private.current == _private.leavingObject) {
return CancelablePromise.resolve();
}
_private.leavingObject = _private.current;
const leaveEventParam = this._generateEventParam(path, origin, object, options);
// Notify before leave level event
this._notifyEvent(EventType.BeforeLeaveLevel, leaveEventParam);
_private.changingOptions = leaveEventParam;
// level control promise
const leavePromises = [];
const leaveParam = Object.assign({}, leaveEventParam);
leaveParam.levelControls = this.getControlsByObject(_private.current);
const controlPromise = _private.current.level.leave(leaveParam);
leavePromises.push(controlPromise);
if (!this.levelControlManager.controlCount) {
// event promise
const eventPromise = new Promise((resolve) => {
_private.app.once(EventType.ObjectLevelComplete, () => {
resolve();
});
this._notifyEvent(EventType.LeaveObjectLevel, leaveEventParam);
}, cLeaveLevelCompleteEventTag);
leavePromises.push(eventPromise);
}
return new CancelablePromise((resolve, reject, onCancel) => {
let canceled = false;
onCancel(() => {
canceled = true;
});
Promise.all(leavePromises).then(() => {
if (canceled) {
return;
}
// Notify leave level event
this._notifyEvent(EventType.LeaveLevel, leaveEventParam);
// Notify after leave level event
this._notifyEvent(EventType.AfterLeaveLevel, leaveEventParam);
_private.leavingObject = null;
_private.changingOptions = null;
resolve();
}).catch(reject);
})
}
_changeObject(object, options, path, origin) {
let _private = this[__.private];
return new CancelablePromise((resolve, reject, onCancel) => {
let leavePromise = null;
let enterPromise = null;
onCancel((cancelParam) => {
if (leavePromise) {
leavePromise.cancel();
}
if (enterPromise) {
enterPromise.cancel(cancelParam);
}
});
// Start to change
_private.flags.enable(Flag.Changing, true);
// Exit current level
leavePromise = this._leaveObjectLevel(object, options, path, origin);
leavePromise.then(() => {
this._enterObjectLevel(object, options, path, origin).then(() => {
resolve();
enterPromise = null;
});
leavePromise = null;
});
});
}
_generateEventParam(path, origin, next, options) {
let _private = this[__.private];
return {
path,
origin,
prev: _private.prev,
current: _private.current,
next,
options
};
}
_startToChange(path, options) {
let _private = this[__.private];
// Try to filter path
let onFilterPath = _private.onFilterPath;
if (onFilterPath) {
path = onFilterPath(path) || path;
}
let origin = _private.current;
path.forEachAsync(obj => {
let promise = this._changeObject(obj, options, path, origin);
_private.pendingPromises.push(promise);
return promise;
});
}
_change(object, options) {
let _private = this[__.private];
// Get the current level
let current = _private.current;
// Skip to change for the same object level
if (current == object) {
return;
}
if (current && !current.destroyed) {
// Get path from current/root level to object
let path = current.getPathTo(object) || app.root.getPathTo(object);
// Start to change level
this._startToChange(path, options);
}
else {
// Get the level path from parents
let path = object.parents.reverse();
path.push(object);
// Start to change level
this._startToChange(path, options);
}
}
_cancelPendingPromises(object, options, path) {
let _private = this[__.private];
if (!_private.pendingPromises.length) {
return;
}
if (!_private.current) {
return;
}
let levelPath = path || _private.current.getPathTo(object);
let levelControls = this.getControlsByObject(_private.current);
let cancelParam = {
origin: _private.current,
prev: _private.prev,
current: _private.current,
next: object,
path: levelPath,
options,
levelControls
}
_private.pendingPromises.forEach(promise => {
promise.cancel(cancelParam);
});
const changeOptions = _private.changeOptions;
if (changeOptions) {
let onStop = Utils.parseValue(changeOptions['onStop'], changeOptions['stop']);
if (onStop) {
onStop();
}
_private.changeOptions = null;
}
// Record options
_private.changeOptions = options;
_private.pendingPromises.length = 0;
}
_createCancelablePromise(object, options) {
return new CancelablePromise((resolve, reject, onCancel) => {
onCancel(() => { });
let copyOptions = Object.assign({}, options);
let onComplete = options['onComplete'] || options['complete'];
let onStop = options['onStop'] || options['stop'];
copyOptions.onComplete = copyOptions.complete = (ev) => {
if (onComplete) {
onComplete(ev);
}
resolve('onComplete');
};
copyOptions.onStop = copyOptions.stop = (ev) => {
if (onStop) {
onStop(ev);
}
resolve('onStop');
}
this._change(object, copyOptions);
});
}
// #endregion
dispose() {
let _private = this[__.private];
let app = _private.app;
app.off(EventType.AfterDestroy, '*', cDestroyObjectEventTag);
this._cancelPendingPromises(null);
_private.flags.clear();
_private.prev = null;
_private.current = null;
this.removeAllComponents(true);
}
/**
* 层级改变时调用的函数
* @callback LevelChangedCallback
* @public
* @param {object} ev 回调参数
* @param {THING.BaseObject} ev.current 当前层级
* @param {THING.BaseObject} ev.prev 上一层级
* @param {Array<THING.BaseObject>} ev.path层级切换的路径(都经过了哪些层级)
*/
/**
* 层级切换参数
* @typedef {object} LevelChangeOptions
* @public
* @property {LevelChangedCallback} onStop 停止切换层级的回调
* @property {LevelChangedCallback} onComplete 最终切换完成的回调
*/
/**
* 切换层级
* @param {THING.BaseObject} object 要切换到的层级对象
* @param {LevelChangeOptions} options 层级切换的参数
* @public
* @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);
* }
* });
*/
change(object, options = {}) {
return this.changeAsync(object, options);
}
/**
* 异步模式切换当前层级。
* @param {THING.BaseObject} object 目标对象。
* @param {LevelChangeOptions} options 选项。
* @returns {Promise<any>}
* @example
* let app = THING.App.current;
* let level = app.level;
* let target = app.query('.Entity')[0];
* level.changeAsync(target, {
* onComplete: function(){
* let ret = level.current == target;
* // @expect(ret == true);
* }
* });
*/
changeAsync(object, options = {}) {
let _private = this[__.private];
let app = _private.app;
// Skip for disable state
if (!_private.enable) {
return Promise.resolve();
}
// Skip to change for the same object level
let current = _private.current;
if (current == object) {
return Promise.resolve();
}
// Ignore level
if (object.tags.has('IgnoreLevel')) {
return Promise.resolve();
}
let pathStart = current;
if (!current || current.destroyed) {
pathStart = app.root;
}
let path = null;
if (pathStart.isRootObject && pathStart === object) {
path = [object]
}
else {
path = pathStart.getPathTo(object);
}
for (let i = 0; i < path.length; i++) {
const element = path[i];
if (element.tags.has('IgnoreLevel')) {
return Promise.resolve();
}
}
this._cancelPendingPromises(object, options, path);
// Start to change level
const promise = this._createCancelablePromise(object, options);
_private.pendingPromises.push(promise);
return promise;
}
/**
* 回退到上一层级
* @param {LevelChangeOptions} options 层级切换参数
* @public
* @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);
* level.back({
* onComplete: function(){
* ret = level.current == target.parent;
* // @expect(ret == true)
* }
* });
* }
* });
*/
back(options = {}) {
let _private = this[__.private];
let current = _private.current;
if (!current) {
return Promise.resolve();
}
let parent = current.parent;
if (!parent) {
return Promise.resolve();
}
return this.changeAsync(parent, options);
}
/**
* 异步模式切换到父级层级。
* @param {LevelChangeOptions} options 选项。
* @returns {Promise<any>}
* @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);
* level.backAsync({
* onComplete: function(){
* ret = level.current == target.parent;
* // @expect(ret == true)
* }
* });
* }
* });
*/
backAsync(options = {}) {
let _private = this[__.private];
// Skip for disable state
if (!_private.enable) {
return;
}
let current = _private.current;
if (!current) {
return Promise.resolve();
}
let parent = current.parent;
if (!parent) {
return Promise.resolve();
}
this._cancelPendingPromises(parent, options);
const promise = this._createCancelablePromise(parent, options);
_private.pendingPromises.push(promise);
return promise;
}
/**
* 退出层级
* @returns {Promise<any>}
* @public
* @example
* let app = THING.App.current;
* let level = app.level;
* level.quit({
* onComplete: function(){
* let ret = level.current == null;
* // @expect(ret == true);
* }
* });
*/
quit() {
let _private = this[__.private];
_private.flags.clear();
// Exit current level
if (_private.current) {
this._cancelPendingPromises(_private.current, null);
let params = {
origin: null,
prev: _private.prev,
current: _private.current,
next: null,
};
this._notifyEvent(EventType.BeforeLeaveLevel, params);
let levelControls = this.getControlsByObject(_private.current);
return _private.current.level.leave({
path: [_private.current],
origin: null,
prev: null,
current: _private.current,
next: null,
levelControls
}).then(() => {
if (_private.pendingPromises.length) {
return;
}
this._notifyEvent(EventType.AfterLeaveLevel, params);
_private.prev = null;
_private.current = null;
});
}
else {
return Promise.resolve();
}
}
/**
* 开启/禁用 层级管理器
* Enable/Disable level manager.
* @type {boolean}
* @example
* let app = THING.App.current;
* let level = app.level;
* level.enable = false;
* let target = app.query('.Entity')[0];
* level.change(target);
* let ret = level.isChanging;
* // @expect(ret == false);
*/
get enable() {
let _private = this[__.private];
return _private.enable;
}
set enable(value) {
let _private = this[__.private];
_private.enable = value;
}
/**
* 开始获取对象层级路径时要调用的函数。
* @callback FilterLevelPathCallback
* @param {Array<THING.BaseObject>} path 层级路径。
* @returns {Array<THING.BaseObject>} 新的层级路径,返回 null 或 undefined 表示使用当前层级路径。
*/
/**
* 获取/设置过滤路径的回调函数。
* @type {FilterLevelPathCallback}
* @private
*/
get onFilterPath() {
let _private = this[__.private];
return _private.onFilterPath;
}
set onFilterPath(value) {
let _private = this[__.private];
_private.onFilterPath = value;
}
/**
* 获取上一层级
* @type {THING.BaseObject}
* @public
* @example
* let app = THING.App.current;
* let level = app.level;
* let prev = level.prev;
* let target = app.query('.Entity')[0];
* level.change(target, {
* onComplete: function(){
* let ret = level.prev == prev;
* // @expect(ret == true);
* }
* });
*/
get prev() {
let _private = this[__.private];
return _private.prev;
}
/**
* 获取当前层级
* @type {THING.BaseObject}
* @public
* @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);
* }
* });
*/
get current() {
let _private = this[__.private];
return _private.current;
}
/**
* 是否正在切换层级
* @type {boolean}
* @public
* @example
* let app = THING.App.current;
* let level = app.level;
* let target = app.query('.Entity')[0];
* level.change(target);
* let ret = level.isChanging;
* // @expect(ret == true);
*/
get isChanging() {
let _private = this[__.private];
return _private.flags.has(Flag.Changing);
}
get changingOptions() {
let _private = this[__.private];
return _private.changingOptions;
}
}
export { LevelManager }