state-transition-system
Version:
An infinite state machine.
107 lines (92 loc) • 2.59 kB
JavaScript
module.exports = StateMachine
var Events = require('events').EventEmitter
var extend = require('extend')
function StateMachine(initialState) {
Events.call(this)
this.state = initialState || 'null'
this.transitions = []
}
StateMachine.prototype = extend({}, Events.prototype, {
state: 'null'
, addTransition: addTransition
// become is kept for backwards compatibility
, become: changeState
, changeState: changeState
, release: release
})
function changeState(state) {
var transitions = calcTransitions(this.transitions, this.state, state)
var args = makeArgs(this.state, state, [].slice.call(arguments, 1))
for (var i = 0; i < transitions.length; i++) {
var result = transitions[i].apply(null, args)
if (result === false) {
this.emit.apply(this, makeArgs('transitionblocked', args,
transitions[i]))
this.emit.apply(this, makeArgs('transitionblocked:' + state, args,
transitions[i]))
return this;
}
}
this.state = state
this.emit.apply(this, makeArgs('changestate', args))
this.emit.apply(this, makeArgs('changestate:' + state,
[].slice.call(arguments)))
return this
}
function release() {
this.removeAllListeners()
}
function addTransition(from, to) {
for (var i = 2; i < arguments.length; i++) {
this.transitions.push({
from: from
, to: to
, fn: arguments[i]
})
}
return this
}
function calcTransitions(transitions, from, to) {
var fns = []
for(var i = 0; i < transitions.length; i++) {
if (checkRule(transitions[i].from, from) &&
checkRule(transitions[i].to, to)) {
fns.push(transitions[i].fn)
}
}
return fns
}
function checkRule(rule, state) {
if (state === null) {
state = 'null'
}
// 'loading' === 'loading'
if (state === rule) {
return true
}
// Straight wildcard
if (rule === '*') {
return true
}
// Starts with wildcard
if (rule[0] === '*') {
var excludes = rule.split('!').slice(1)
return !(excludes.indexOf(state) + 1)
}
// No match
return false
}
function makeArgs() {
var args = []
for (var i = 0; i < arguments.length; i++) {
if (Array.isArray(arguments[i])) {
for (var j = 0; j < arguments[i].length; j++) {
args.push(arguments[i][j])
}
}
else {
args.push(arguments[i])
}
}
return args
}