@wiz-code/async-fsm
Version:
Finite StateMachine JavaScript Library
439 lines (330 loc) • 13.5 kB
JavaScript
'use strict';
var _ = require('underscore');
var Elem = require('./elem');
var logger = require('./logger');
var util = require('./util');
var Region = function (name, options) {
Elem.call(this, name);
this._type = 'region';
options = options || {};
if (!_.isUndefined(options.data)) {
this.set(options.data);
}
this.save();
if (!_.isUndefined(options.props)) {
this.setProp(options.props);
}
if (!_.isUndefined(options.methods)) {
this.setMethod(options.methods);
}
this._initialPseudo = null;
this._final = null;
this._historyPseudo = null;
this._previousState = null;
this.children = {
states: [],
transitions: [],
};
this.setObserverType('states', 'transits');
this._setDefaultStates();
};
Region.prototype = _.create(Elem.prototype, {
constructor: Region,
_cname: 'Region',
setName: function (name, automatical) {
this._name = name;
if (!this._originalName && util.isFalsy(automatical)) {
this._originalName = name;
}
this._setDefaultStateName();
return name;
},
hasHistory: function (deep) {
return util.isFalsy(deep) ? !_.isNull(this._historyPseudo) :
!_.isNull(this._historyPseudo) && this._historyPseudo._isDeep;
},
getIndex: function () {
var result = -1;
if (!_.isNull(this.parent)) {
result = _.indexOf(this.parent.children, this);
}
return result;
},
getStateByName: function (stateName) {
return _.find(this.children.states, function (state) {
return state.getName() === stateName;
});
},
getTransitionByName: function (transitionName) {
return _.find(this.children.transitions, function (transit) {
return transit.getName() === transitionName;
});
},
getStateById: function (stateId) {
return _.find(this.children.states, function (state) {
return state.getId() === stateId;
});
},
getTransitionById: function (transitionId) {
return _.find(this.children.transitions, function (transit) {
return transit.getId() === transitionId;
});
},
findActiveState: function () {
return _.find(this.children.states, function (state) {
return state.isActive();
});
},
addState: function () {
var BaseState, Machine, InitialPseudoState, HistoryPseudoState, FinalState, states, i, l, state, currentDepth;
BaseState = require('./base-state');
Machine = require('./machines').Machine;
HistoryPseudoState = require('./pseudo-states').HistoryPseudoState;
InitialPseudoState = require('./pseudo-states').InitialPseudoState;
FinalState = require('./states').FinalState;
if (this._attached) {
logger.error('デプロイ後は要素の追加/削除はできません。Machineクラスのundeploy()メソッドでデプロイを取り消してください。');
}
states = _.toArray(arguments);
for (i = 0, l = states.length; i < l; i += 1) {
state = states[i];
if (state instanceof Machine || !(state instanceof BaseState)) {
logger.error('Stateインスタンスを指定してください。');
}
if (state instanceof InitialPseudoState) {
this._initialPseudo = state;
} else if (state instanceof FinalState) {
this._final = state;
} else if (state instanceof HistoryPseudoState) {
this._historyPseudo = state;
}
this.children.states.push(state);
this.children.states[state._id] = state;
this.addObserver('children', state);
this.addObserver('states', state);
state.container = this;
state.addObserver('container', this);
currentDepth = this._getParentDepth() + 1;
state._refresh(currentDepth);
}
return states.length > 1 ? states : _.first(states);
},
/* 引数にStateインスタンスを複数指定可 */
removeState: function () {
var HistoryPseudoState, states, i, state;
HistoryPseudoState = require('./pseudo-states').HistoryPseudoState;
if (this._attached) {
logger.error('デプロイ後は要素の追加/削除はできません。Machineクラスのundeploy()メソッドでデプロイを取り消してください。');
}
states = _.toArray(arguments);
for (i = this.children.states.length; i--;) {
state = this.children.states[i];
if (_.indexOf(states, state) > -1) {
if (state instanceof HistoryPseudoState) {
this._historyPseudo = null;
}
this.children.states.splice(i, 1);
delete this.children.states[state._id];
this.removeObserver('children', state);
this.removeObserver('states', state);
state.container = null;
state.removeObserver('container', this);
state._refresh(0);
}
}
return states.length > 1 ? states : _.first(states);
},
addTransition: function () {
var InitialPseudoState, FinalState, Transition, transits, i, l, transit, result, currentDepth;
InitialPseudoState = require('./pseudo-states').InitialPseudoState;
FinalState = require('./states').FinalState;
Transition = require('./transition');
if (this._attached) {
logger.error('デプロイ後は要素の追加/削除はできません。Machineクラスのundeploy()メソッドでデプロイを取り消してください。');
}
transits = _.toArray(arguments);
for (i = 0, l = transits.length; i < l; i += 1) {
transit = transits[i];
if (!(transit instanceof Transition)) {
logger.error('Transitionインスタンスを指定してください。');
}
if (transit._rawSource === InitialPseudoState || util.isFalsy(transit._rawSource)) {
transit.source = this._initialPseudo;
} else {
result = util.findState(this, transit._rawSource, 0);
if (_.isUndefined(result)) {
result = util.findState(this, transit._rawSource, 1);
if (!_.isUndefined(result)) {
transit._exitViaExitPoint = true;
} else {
logger.error('遷移元のStateインスタンスが見つかりません。');
}
}
transit.source = transit._rawSource;
}
if (transit._rawTarget === FinalState || util.isFalsy(transit._rawTarget)) {
transit.target = this._final;
} else {
result = util.findState(this, transit._rawTarget, 0);
if (_.isUndefined(result)) {
result = util.findState(this, transit._rawTarget, 1);
if (!_.isUndefined(result)) {
transit._isExplicitEntry = true;
} else {
logger.error('遷移先のStateインスタンスが見つかりません。');
}
}
transit.target = transit._rawTarget;
}
transit.addObserver('source', transit.source);
transit.addObserver('target', transit.target);
if (!transit._originalName) {
transit.setName('transit-from-' + transit.source._name + '-to-' + transit.target._name, true);
}
this.children.transitions.push(transit);
this.children.transitions[transit._id] = transit;
this.addObserver('children', transit);
this.addObserver('transits', transit);
transit.container = this;
transit.addObserver('container', this);
currentDepth = this._getParentDepth() + 1;
transit._refresh(currentDepth);
}
return transits.length > 1 ? transits : _.first(transits);
},
removeTransition: function () {
var transits, i, transit;
if (this._attached) {
logger.error('デプロイ後は要素の追加/削除はできません。Machineクラスのundeploy()メソッドでデプロイを取り消してください。');
}
transits = _.toArray(arguments);
for (i = this.children.transitions.length; i--;) {
transit = this.children.transitions[i];
if (_.indexOf(transits, transit) > -1) {
this.children.transitions.splice(i, 1);
delete this.children.transitions[transit._id];
this.removeObserver('children', transit);
this.removeObserver('transits', transit);
transit.removeObserver('source', transit.source);
transit.removeObserver('target', transit.target);
if (!transit._originalName) {
transit.setName(transit._id, true);
}
transit.container = null;
transit.removeObserver('container', this);
transit._refresh(0);
}
}
return transits.length > 1 ? transits : _.first(transits);
},
update: function (event) {
var params = _.toArray(arguments).slice(1);
switch (event) {
case 'entry':
this._entry.apply(this, params);
break;
case 'exit':
this._exit.apply(this, params);
break;
case 'refresh':
this._refresh.apply(this, params);
break;
case 'completion':
this._completion.apply(this, params);
break;
case 'set-previous-state':
this._setPreviousState.apply(this, params);
break;
}
},
_getParentDepth: function () {
var result = -1;
if (!_.isNull(this.parent)) {
result = this.parent._depth;
}
return result;
},
_getUpperContainer: function () {
var result = null;
if (!_.isNull(this.parent) && !_.isNull(this.parent.container)) {
result = this.parent.container;
}
return result;
},
_setDefaultStates: function () {
var InitialPseudoState = require('./pseudo-states').InitialPseudoState;
var FinalState = require('./states').FinalState;
var initialPseudo, final;
if (!_.isNull(this._initialPseudo) || !_.isNull(this._final)) {
return;
}
initialPseudo = new InitialPseudoState(false);
final = new FinalState(false);
this.addState(initialPseudo, final);
this._setDefaultStateName();
},
_setDefaultStateName: function () {
this._initialPseudo.setName('initial-pseudo-state-in-' + this._name, true);
this._final.setName('final-state-in-' + this._name, true);
},
_refresh: function (depth) {
var currentDepth;
depth = !_.isUndefined(depth) ? depth : this._getParentDepth();
currentDepth = depth + 1;
this.notify('children', 'refresh', currentDepth);
},
_setPreviousState: function (state) {
this._previousState = state;
return state;
},
_entry: function (message) {
var SubMachine, PseudoState, state;
SubMachine = require('./machines').SubMachine;
PseudoState = require('./pseudo-states').PseudoState;
if (!this.isActive()) {
this._activate();
if (_.indexOf(this.children.states, message.priority) > -1) {
state = message.priority;
message.priority = null;
} else if (this.parent instanceof SubMachine) {
this.parent.notify('inner-machine', 'link-forward');
return;
} else {
if (message.deepHistory) {
state = this._previousState || this._initialPseudo;
} else if (!_.isNull(this._historyPseudo)) {
state = this._historyPseudo;
if (state._isDeep) {
message.deepHistory = true;
}
} else {
state = this._initialPseudo;
}
}
if (!(state instanceof PseudoState)) {
this._async(function () {
state.update('entry', message);
});
} else {
state.update('entry', message);
}
}
},
_exit: function () {
if (this.isActive()) {
this.notify('states', 'exit');
this._deactivate();
}
},
_completion: function () {
this._deactivate();
if (!_.isNull(this.parent)) {
if (_.every(this.parent.children, function (region) {
return !region.isActive();
})) {
this.notify('parent', 'completion');
}
}
},
});
module.exports = Region;