import { Flags } from '@uino/base-thing';
import { Utils } from './Utils';
const __ = {
private: Symbol('private'),
}
const Flag = {
Enable: 1 << 0,
Running: 1 << 1,
}
// #region Private Functions
function _onSort(a, b) {
if (a.priority !== b.priority) {
return b.priority - a.priority;
}
return a.index - b.index;
};
// Get action index by name.
function _getActionIndexByName(actions, name) {
for (let i = 0; i < actions.length; i++) {
let action = actions[i];
if (action.name != name) {
continue;
}
return i;
}
return -1;
}
// Get action index by type.
function _getActionIndexByType(actions, type) {
for (let i = 0; i < actions.length; i++) {
let action = actions[i];
if (action.processor instanceof type === false) {
continue;
}
return i;
}
return -1;
}
// #endregion
/**
* @class ActionQueue
* The action queue.
* @memberof THING
* @public
*/
class ActionQueue {
/**
* The action queue what can process multiple actions.
* @param {Object} param The initial parameters.
* @example
* let actionQueue = new THING.ActionQueue({ name: 'MyActionQueue' });
* @public
*/
constructor(param = {}) {
this[__.private] = {};
let _private = this[__.private];
_private.name = Utils.parseValue(param['name'], '');
_private.actions = [];
_private.actionRuntimeData = {};
}
// #region Private
// Get action by name.
_getActionByName(name) {
let _private = this[__.private];
let actions = _private.actions;
let index = _getActionIndexByName(actions, name);
if (index === -1) {
return null;
}
return actions[index];
}
_getActionByType(type) {
let _private = this[__.private];
let actions = _private.actions;
for (let i = 0; i < actions.length; i++) {
let action = actions[i];
if (action.processor instanceof type) {
return action;
}
}
return null;
}
_addAction(processor, name, priority) {
let _private = this[__.private];
let actions = _private.actions;
// Create flags
let flags = new Flags();
flags.enable(Flag.Enable, true);
// Create action
let action = {
flags,
processor,
name: Utils.parseValue(name, ''),
priority: Utils.parseValue(priority, 0),
index: actions.length
};
// Add the actions and sort it by priority
actions.push(action);
actions.sort(_onSort);
}
// #endregion
/**
* Clear all groups and result.
* @example
* let actionQueue = new THING.ActionQueue({ name: 'MyActionQueue' });
* class ActionProcessor {
* onStop() {
* }
* onRun(options) {
* }
* };
* actionQueue.add(new ActionProcessor(), 'FirstActionProcessor', 1000);
* actionQueue.clear();
* // @expect(actionQueue.actions.length == 1);
* @public
*/
clear() {
this.stop();
let _private = this[__.private];
_private.actions.forEach(action => {
// Stop to run
action.flags.enable(Flag.Running, true);
let onStop = action.processor.onStop;
if (onStop) {
onStop.call(action.processor);
}
});
_private.actions.length = 0;
_private.actionRuntimeData = {};
}
/**
* The function to call when stop action.
* @callback OnActionProcessorStop
*/
/**
* The function to call when run action.
* @callback OnActionProcessorRun
* @param {Object} options The options.
* @returns {Promise<any>}
*/
/**
* The function to call when run action.
* @callback OnActionProcessorEnable
* @param {Boolean} value The value.
*/
/**
* @typedef {Object} ActionProcessor
* @property {OnActionProcessorStop} onStop
* @property {OnActionProcessorRun} onRun
* @property {OnActionProcessorEnable} onEnable
*/
/**
* Add action.
* @param {Array<ActionProcessor>|ActionProcessor} processor The action processor(s).
* @param {String} name The action name(only works for single processor mode).
* @param {Number} priority The action priority value(default is 0, higher value indicates higher priority).
* @example
* // Create action processor
* class ActionProcessor {
* onStop() {
* }
*
* onRun(options) {
* }
* };
*
* actionQueue.add(new ActionProcessor(), 'FirstActionProcessor', 1000);
* @public
*/
add(processor, name = '', priority = 0) {
if (!processor) {
return;
}
if (!Utils.isString(name)) {
Utils.error(`Add action failed, due to the name('${name}') is not string type`);
return;
}
if (!Utils.isNumber(priority)) {
Utils.error(`Add action failed, due to the priority('${priority}') is not number type`);
return;
}
if (Utils.isArray(processor)) {
processor.forEach(action => {
this._addAction(action);
});
}
else {
this._addAction(processor, name, priority);
}
}
/**
* Set action.
* @param {String} name The action name.
* @param {Object} options The options.
* @param {Number} options.priority The action priority value(default is 0, higher value indicates higher priority).
* @param {ActionProcessor} options.processor The action processor.
* @private
*/
set(name, options) {
let _private = this[__.private];
let priority = options['priority'];
let processor = options['processor'];
let action = this._getActionByName(name);
if (action) {
// Backup previous options
let prePriority = action.priority;
// Update action options
action.priority = Utils.parseValue(priority, action.priority);
action.processor = Utils.parseValue(processor, action.processor);
// If priority changed the we need to resort it
if (prePriority != action.priority) {
_private.actions.sort(_onSort);
}
}
else {
this._addAction(processor, name, priority);
}
}
/**
* Get action by name.
* @param {String} name The action name.
* @returns {ActionProcessor}
* @example
* let actionProcessor = actionQueue.getByName('FirstActionProcessor');
* @public
*/
getByName(name) {
let action = this._getActionByName(name);
if (!action) {
return null;
}
return action.processor;
}
/**
* Get action by type.
* @param {*} type The action type.
* @returns {ActionProcessor}
* @example
* // ActionProcess is class type(class ActionProcessor)
* let actionProcessor = actionQueue.getByType(ActionProcessor);
* @public
*/
getByType(type) {
let action = this._getActionByType(type);
if (!action) {
return null;
}
return action.processor;
}
/**
* Remove action by name.
* @param {String} name The action name.
* @example
* actionQueue.removeByName('FirstActionProcessor');
* @public
*/
removeByName(name) {
let _private = this[__.private];
let actions = _private.actions;
let index = _getActionIndexByName(actions, name);
if (index === -1) {
return;
}
actions._removeAt(index);
}
/**
* Remove action by type.
* @param {*} type The action type.
* @example
* // ActionProcess is class type(class ActionProcessor)
* actionQueue.removeByType(ActionProcessor);
* @public
*/
removeByType(type) {
let _private = this[__.private];
let actions = _private.actions;
let index = _getActionIndexByType(actions, type);
if (index === -1) {
return;
}
actions._removeAt(index);
}
/**
* The function to call when traverse action processor.
* @callback OnTraverseActionProcessorCallback
*/
/**
* Traverse by name.
* @param {String} name The action name.
* @param {OnTraverseActionProcessorCallback} callback The callback function.
* @example
* actionQueue.traverseByName('PlayAction', (actionProcessor) => {
* console.log(actionProcessor);
* });
* @public
*/
traverseByName(name, callback) {
let _private = this[__.private];
let actions = _private.actions;
for (let i = 0; i < actions.length; i++) {
let action = actions[i];
if (action.name == name) {
callback(action.processor);
}
}
}
/**
* Traverse by type.
* @param {*} type The action type.
* @param {OnTraverseActionProcessorCallback} callback The callback function.
* @example
* // ActionProcess is class type(class ActionProcessor)
* actionQueue.traverseByType(ActionProcess, (actionProcessor) => {
* console.log(actionProcessor);
* });
* @public
*/
traverseByType(type, callback) {
let _private = this[__.private];
let actions = _private.actions;
for (let i = 0; i < actions.length; i++) {
let action = actions[i];
if (action.processor instanceof type) {
callback(action.processor);
}
}
}
/**
* Replace action.
* @param {ActionProcessor} desProcessor The target processor to be replaced.
* @param {ActionProcessor} srcProcessor The source processor to replace.
* @returns {Boolean}
* @private
*/
replace(desProcessor, srcProcessor) {
let _private = this[__.private];
let actions = _private.actions;
for (let i = 0; i < actions.length; i++) {
let action = actions[i];
if (action.processor != desProcessor) {
continue;
}
let onStop = action.processor.onStop;
if (onStop) {
onStop.call(action.processor);
}
action.processor = srcProcessor;
return true;
}
return false;
}
/**
* Get action priority.
* @param {ActionProcessor} processor The action processor.
* @returns {Number}
* @private
*/
getPriority(processor) {
let _private = this[__.private];
let actions = _private.actions;
for (let i = 0; i < actions.length; i++) {
let action = actions[i];
if (action.processor != processor) {
continue;
}
return action.priority;
}
return 0;
}
/**
* Set action priority.
* @param {ActionProcessor} processor The action processor.
* @param {Number} priority The action priority.
* @returns {Boolean}
* @private
*/
setPriority(processor, priority) {
let _private = this[__.private];
let actions = _private.actions;
for (let i = 0; i < actions.length; i++) {
let action = actions[i];
if (action.processor != processor) {
continue;
}
action.priority = priority;
actions.sort(_onSort);
return true;
}
return false;
}
/**
* Enable/Disable action.
* @param {String} name The action name.
* @param {Boolean} value The action enable state.
* @private
*/
enable(name, value) {
let action = this._getActionByName(name);
if (!action) {
return;
}
if (action.flags.enable(Flag.Enable, value)) {
let processor = action.processor;
if (processor.onEnable) {
processor.onEnable(value);
}
}
}
/**
* Check whether enable action or not.
* @param {String} name The action name.
* @returns {Boolean}
* @private
*/
isEnabled(name) {
let action = this._getActionByName(name);
if (!action) {
return false;
}
return action.flags.has(Flag.Enable);
}
/**
* Stop.
* @private
*/
stop() {
let _private = this[__.private];
let actions = _private.actions;
actions.forEach(action => {
let flags = action.flags;
if (!flags.has(Flag.Running)) {
return;
}
let processor = action.processor;
if (!processor) {
return;
}
// Record the runtime data of the actions
if (processor.getRuntimeData) {
const data = processor.getRuntimeData();
_private.actionRuntimeData[action.name] = data;
}
// Reset the action runtime data
processor.resetRuntimeData();
let onStop = processor.onStop;
if (onStop) {
onStop.call(processor);
}
flags.enable(Flag.Running, false);
});
}
/**
* Run.
* @param {Object} options The options.
* @returns {Promise<any>}
* @private
*/
run(options = {}) {
let _private = this[__.private];
this.stop();
return new Promise((resolve, reject) => {
const actions = _private.actions;
if (actions.length) {
let abort = false;
actions.forEachAsync((action, index) => {
if (abort) {
return;
}
// Start to run
action.flags.enable(Flag.Running, true);
// Get action processor
if (Utils.isFunction(action.processor)) {
action.processor = action.processor();
}
// Get action processor
let processor = action.processor;
if (!processor) {
return;
}
// Recover the runtime data of the action
if (processor.setRuntimeData) {
const data = _private.actionRuntimeData[action.name];
if (data) {
processor.setRuntimeData(data)
}
}
// Check whether enable processor or not
if (!action.flags.has(Flag.Enable)) {
return;
}
// Run action by processor
let promise = processor.onRun(options);
if (!promise) {
// Check whether it's the last or abort action
if (processor.isAbort || index == actions.length - 1) {
abort = true;
resolve();
}
return;
}
// Wait to finish
return promise.then(() => {
// Check whether it's the last or abort action
if (processor.isAbort || index == actions.length - 1) {
abort = true;
resolve();
}
}).catch(ex => {
reject(ex);
});
});
}
else {
resolve();
}
});
}
/**
* Get/Set name.
* @type {String}
* @example
* // Print action queue's name
* console.log(actionQueue.name);
* @public
*/
get name() {
let _private = this[__.private];
return _private.name;
}
set name(value) {
let _private = this[__.private];
_private.name = value;
}
/**
* Get actions.
* @type {Array<ActionProcessor>}
* @example
* // Print action queue's (actions/processors)
* console.log(actionQueue.actions);
* @public
*/
get actions() {
let _private = this[__.private];
return _private.actions;
}
}
export { ActionQueue }