UNPKG

@wiz-code/async-fsm

Version:

Finite StateMachine JavaScript Library

429 lines (324 loc) 12.1 kB
'use strict'; var Promise = require('es6-promise').Promise; var _ = require('underscore'); var BaseState = require('./base-state'); var logger = require('./logger'); var util = require('./util'); var Machine = function (name, options) { BaseState.call(this, name, options); 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._deployed = false; this._chain = { main: null, activities: [], counter: 0, }; this.appendRegion(); this.setObserverType('outer-machine'); }; Machine.prototype = _.create(BaseState.prototype, { constructor: Machine, _cname: 'Machine', deploy: function () { var Transition, ConnectionPointPseudoState; Transition = require('./transition'); ConnectionPointPseudoState = require('./pseudo-states').ConnectionPointPseudoState; this._deployed = true; this._chain.main = Promise.resolve(); this._refresh(this._depth); util.eachElem(this, _.bind(function (elem) { elem._attached = true; elem.root = this; if (!(elem instanceof Machine)) { elem.addObserver('root', this); if (elem instanceof BaseState || elem instanceof Transition) { elem.addObserver('parent', elem.parent); } } if (elem instanceof ConnectionPointPseudoState) { if (elem.parent === this) { elem._isConnector = true; } } }, this)); return this; }, undeploy: function () { var Transition = require('./transition'); this._deployed = false; this._chain.main = null; util.eachElem(this, _.bind(function (elem) { elem._attached = false; elem.root = null; if (!(elem instanceof Machine)) { elem.removeObserver('root', this); if (elem instanceof BaseState || elem instanceof Transition) { elem.removeObserver('parent', elem.parent); } } }, this)); this._refresh(this._depth); return this; }, start: function (priority) { var message = { priority: null, deepHistory: false, }; if (!_.isUndefined(priority)) { message.priority = priority; } if (!this._deployed) { logger.error('start()の前にdeploy()メソッドを実行してください。'); } if (!this.isActive()) { logger.info('Machineインスタンス"' + this._name + '"が動作を開始しました。'); this._addChain(_.bind(function () { this._entry(message); return Promise.resolve(); }, this)); } else { logger.warn('Machineインスタンス"' + this._name + '"はすでに起動しています。'); } return this; }, finish: function () { if (!this._deployed) { logger.error('deploy()メソッドがまだ実行されていません。'); } if (this.isActive()) { this.completion(); } else { logger.warn('Machineインスタンス"' + this._name + '"はすでに動作を終了しています。'); } return this; }, completion: function () { if (!this.isActive()) { logger.error(this._cname + 'インスタンス"' + this._name + '"はすでに非アクティブ化されています。'); } this._addChain(_.bind(function () { this._exit(); logger.info('Machineインスタンス"' + this._name + '"が動作を終了しました。'); return Promise.resolve(); }, this)); }, _addChain: function (callback) { this._chain.main = this._chain.main.then(callback).catch(this._onRejected); }, _addActivity: function (callback) { this._chain.activities.push(callback); }, _prevActivity: function (state) { if (_.isNull(this._chain.resolve)) { this._chain.counter += state.children.length; } }, _afterActivity: function (state) { var self = this; if (!state._hasSubState()) { this._chain.counter -= 1; if (this._chain.counter <= 0) { this._chain.main = _.reduce(this._chain.activities, function (chain, activity) { return chain.then(activity).catch(self._onRejected); }, this._chain.main); this._chain.activities.length = 0; this._chain.counter = 0; } } }, _onAborted: function () { logger.info('Machineインスタンス"' + this._name + '"は処理を停止しました。'); }, _onRejected: function (e) { logger.info(e); return Promise.reject(e); }, _inboundTransit: function (key) { var i, l, region, state; if (!_.isUndefined(key)) { if (_.isEmpty(key)) { logger.error('リンク先のEntryPointPseudoStateインスタンスに対応するキーが指定されてません。'); } for (i = 0, l = this.children.length; i < l; i += 1) { region = this.children[i]; state = region.children.states[key]; if (!_.isUndefined(state)) { this.start(state); return; } } logger.error('エンドポイントのEntryPointPseudoStateインスタンスが指定されてません。'); } else { this.start(); } }, _outboundTransit: function (state) { this.completion(); this._addChain(_.bind(function () { this.notify('outer-machine', 'link-back', state.getId()); return Promise.resolve(); }, this)); }, update: function (event, callback) { var params = _.toArray(arguments).slice(1); switch (event) { case 'async': this._addChain(callback); break; case 'async-activity': this._addActivity(callback); break; case 'prev-activity': this._prevActivity.apply(this, params); break; case 'after-activity': this._afterActivity.apply(this, params); break; case 'entry': this._entry.apply(this, params); break; case 'exit': this._exit.apply(this, params); break; case 'completion': this.completion.apply(this, params); break; case 'termination': this._onAborted.apply(this, params); break; case 'link-forward': this._inboundTransit.apply(this, params); break; case 'exit-point': this._outboundTransit.apply(this, params); break; } }, }); var SubMachine = function (name) { BaseState.call(this, name); this._linkedMachine = null; this._deployed = false; this.appendRegion(); this.setObserverType('inner-machine'); }; SubMachine.prototype = _.create(BaseState.prototype, { constructor: SubMachine, _cname: 'SubMachine', deploy: function () { var Region, ConnectionPointPseudoState, InitialPseudoState, FinalState; Region = require('./region'); ConnectionPointPseudoState = require('./pseudo-states').ConnectionPointPseudoState; InitialPseudoState = require('./pseudo-states').InitialPseudoState; FinalState = require('./states').FinalState; this._deployed = true; util.eachElem(this, _.bind(function (elem) { if (elem instanceof ConnectionPointPseudoState) { elem.addObserver('sub-root', this); if (elem.parent === this) { elem._isMediator = true; } else { logger.error('ConnectionPointPseudoStateインスタンスはサブマシン直下のサブ状態でなければなりません。'); } } else if (!(elem instanceof Region || elem instanceof SubMachine || elem instanceof InitialPseudoState || elem instanceof FinalState)) { logger.error('SubMachineインスタンスはConnectionPointPseudoStateクラス以外の状態を追加できません。'); } }, this)); return this; }, undeploy: function () { var ConnectionPointPseudoState, InitialPseudoState, FinalState; ConnectionPointPseudoState = require('./pseudo-states').ConnectionPointPseudoState; InitialPseudoState = require('./pseudo-states').InitialPseudoState; FinalState = require('./states').FinalState; this._deployed = false; util.eachElem(this, _.bind(function (elem) { if (elem instanceof ConnectionPointPseudoState) { elem.removeObserver('sub-root', this); elem._isMediator = false; } }, this)); return this; }, link: function (machine) { if (!(machine instanceof Machine)) { logger.error('Machineインスタンスを指定してください。'); } this._linkedMachine = machine; this.addObserver('inner-machine', machine); machine.addObserver('outer-machine', this); }, unlink: function () { this.removeObserver('inner-machine', this._linkedMachine); this._linkedMachine.removeObserver('outer-machine', this); this._linkedMachine = null; }, update: function (event) { var params = _.toArray(arguments).slice(1); switch (event) { case 'entry-point': this._linkForward.apply(this, params); break; case 'link-back': this._linkBack.apply(this, params); break; case 'entry': this._entry.apply(this, params); break; case 'exit': this._exit.apply(this, params); break; } }, _linkForward: function (state) { this._async(function () { state._exit(); this.notify('inner-machine', 'link-forward', state._key); }); }, _linkBack: function (id) { var exitPoint, i, l, region; for (i = 0, l = this.children.length; i < l; i += 1) { region = this.children[i]; exitPoint = _.findWhere(region.children.states, {_key: id}); if (!_.isUndefined(exitPoint)) { exitPoint._entry(); return; } } logger.error('リンク先のEntryPointPseudoStateインスタンスに対応するキーが指定されてません。'); }, _entry: function (message) { if (!this._deployed) { logger.error('SubMachineインスタンスのdeploy()メソッドを実行してください。'); } if (!this.isActive()) { this._activate(); this.notify('children', 'entry', message); } }, _exit: function () { if (!this._deployed) { logger.error('SubMachineインスタンスのdeploy()メソッドを実行してください。'); } if (this.isActive()) { this.notify('children', 'exit'); this._deactivate(); } }, }); module.exports = { Machine: Machine, SubMachine: SubMachine, };