mooremachine
Advanced tools
Comparing version 1.3.0 to 1.4.0
@@ -36,2 +36,6 @@ // Copyright 2015 Joyent, Inc. | ||
this.fsm_history = []; | ||
this.fsm_validTransitions = undefined; | ||
if (this.fsm_allStateEvents === undefined) | ||
this.fsm_allStateEvents = []; | ||
this.fsm_state = undefined; | ||
EventEmitter.call(this); | ||
@@ -51,2 +55,14 @@ this.gotoState(defState); | ||
FSM.prototype.validTransitions = function (states) { | ||
assert.arrayOfString(states, 'states'); | ||
this.fsm_validTransitions = states; | ||
}; | ||
FSM.prototype.allStateEvent = function (evt) { | ||
assert.string(evt, 'event'); | ||
if (this.fsm_allStateEvents === undefined) | ||
this.fsm_allStateEvents = []; | ||
this.fsm_allStateEvents.push(evt); | ||
} | ||
/* | ||
@@ -81,2 +97,9 @@ * Calls a callback when this FSM reaches a given state. This is for use by | ||
if (this.fsm_validTransitions !== undefined) { | ||
if (this.fsm_validTransitions.indexOf(state) === -1) { | ||
throw (new Error('Invalid FSM transition: ' + | ||
this.fsm_state + ' => ' + state)); | ||
} | ||
} | ||
/* | ||
@@ -115,5 +138,17 @@ * If we're changing to a state that is not a sub-state of this one, | ||
this.fsm_validTransitions = undefined; | ||
f.call(this, this.sOn.bind(this), this.sOnce.bind(this), | ||
this.sTimeout.bind(this), this.sOnState.bind(this)); | ||
var self = this; | ||
this.fsm_allStateEvents.forEach(function (evt) { | ||
if (self.listeners(evt).length < 1) { | ||
throw (new Error('FSM consistency error: ' + | ||
'state entry function for "' + state + '" did ' + | ||
'not add a handler for all-state event "' + | ||
evt + '"')); | ||
} | ||
}); | ||
this.emit('stateChanged', state); | ||
@@ -120,0 +155,0 @@ }; |
{ | ||
"name": "mooremachine", | ||
"version": "1.3.0", | ||
"version": "1.4.0", | ||
"description": "Moore finite state machines", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
158
README.md
@@ -18,5 +18,6 @@ mooremachine | ||
This lets you define sequential actions with asynchronous functions. However, | ||
if you need more complex logic within this structure, this becomes rapidly | ||
limiting -- it is difficult, for example, to create a loop. You have to improvise one by nesting some form of loop within a `series` call and a second | ||
This lets you define sequential actions with asynchronous functions. However, if | ||
you need more complex logic within this structure, this becomes rapidly | ||
limiting -- it is difficult, for example, to create a loop. You have to | ||
improvise one by nesting some form of loop within a `series` call and a second | ||
layer of callbacks. | ||
@@ -65,3 +66,152 @@ | ||
todo | ||
In this example we'll create an FSM called `ThingFSM`. It's a typical network | ||
client, which wants to make a TCP connection to something and talk to it. It | ||
also wants to delay/backoff and retry on failure. | ||
```js | ||
var mod_mooremachine = require('mooremachine'); | ||
var mod_util = require('util'); | ||
var mod_net = require('net'); | ||
function ThingFSM() { | ||
this.tf_sock = undefined; | ||
this.tf_lastError = undefined; | ||
mod_mooremachine.FSM.call(this, 'stopped'); | ||
} | ||
mod_util.inherits(ThingFSM, mod_mooremachine.FSM); | ||
ThingFSM.prototype.state_stopped = function (on) { | ||
var self = this; | ||
on(this, 'startAsserted', function () { | ||
self.gotoState('connecting'); | ||
}); | ||
}; | ||
ThingFSM.prototype.state_connecting = function (on) { | ||
var self = this; | ||
this.tf_sock = mod_net.connect(...); | ||
on(this.tf_sock, 'connect', function () { | ||
self.gotoState('connected'); | ||
}); | ||
on(this.tf_sock, 'error', function (err) { | ||
self.tf_lastError = err; | ||
self.gotoState('error'); | ||
}); | ||
}; | ||
ThingFSM.prototype.state_error = function (on, once, timeout) { | ||
var self = this; | ||
if (this.tf_sock !== undefined) | ||
this.tf_sock.destroy(); | ||
this.tf_sock = undefined; | ||
/* Print an error, do something, check # of retries... */ | ||
/* Retry the connection in 5 seconds */ | ||
timeout(5000, function () { | ||
self.gotoState('connecting'); | ||
}); | ||
}; | ||
ThingFSM.prototype.state_connected = function (on) { | ||
/* ... */ | ||
}; | ||
``` | ||
API | ||
--- | ||
### Inheriting from FSM | ||
Implementations of a state machine should inherit from `mod_mooremachine.FSM`, | ||
using `mod_util.inherits`. The only compulsory methods that the subprototype | ||
must implement are the state callbacks. | ||
### `mod_mooremachine.FSM(initialState)` | ||
Constructor. Must be called by the constructor of the subprototype. | ||
Parameters: | ||
- `initialState`: String, name of the initial state the FSM will enter at | ||
startup | ||
### `mod_mooremachine#state_name(on, once, timeout, onState)` | ||
State entry functions. These run exactly once, at entry to the new state. They | ||
should take any actions associated with the state and set up any callbacks that | ||
can cause transition out of it. | ||
The `on`, `once`, `timeout` and `onState` arguments are functions that should be | ||
used to set up events that can lead to a state transition. The `on` function is | ||
like `EventEmitter#on`, but any handlers set up with it will be automatically | ||
torn down as soon as the FSM leaves its current state. Similar for `once` and | ||
`timeout`. The `onState` function is used in place of calling | ||
`mod_mooremachine#onState` on another FSM. | ||
Parameters: | ||
- `on`: Function `(emitter, event, cb)`, sets up an event callback like | ||
`EventEmitter#on`. Parameters: | ||
- `emitter`: an EventEmitter | ||
- `event`: a String, name of the event | ||
- `cb`: a Function, callback to run when the event happens | ||
- `once`: Function `(emitter, event, cb)`, like `on` but only runs once | ||
- `timeout`: Function `(timeout, cb)`, like `setTimeout()`. Parameters: | ||
- `timeout`: Number, milliseconds until the callback runs | ||
- `cb`: a Function, callback to run | ||
- `onState`: Function `(fsm, state, cb)` | ||
### `mod_mooremachine#validTransitions(possibleStates)` | ||
Should be called from a state entry function. Sets the list of valid transitions | ||
that are possible out of the current state. Any attempt to transition the FSM | ||
out of the current state to a state not on this list (using `gotoState()`) will | ||
throw an error. | ||
Parameters: | ||
- `possibleStates`: Array of String, names of valid states | ||
### `mod_mooremachine#allStateEvent(name)` | ||
Adds an "all-state event". Should be called in the constructor for an FSM | ||
subclass. Any registered all-state event must have a handler registered on it | ||
after any state transition. This allows you to enforce that particular events | ||
must be handled in every state of the FSM. | ||
Parameters: | ||
- `name`: String, name of the event | ||
### `mod_mooremachine#getState()` | ||
Returns a String, full current state of the FSM (including sub-state). | ||
### `mod_mooremachine#isInState(state)` | ||
Tests whether the FSM is in the given state, or any sub-state of it. | ||
Parameters: | ||
- `state`: String, state to test for | ||
Returns a Boolean. | ||
### `mod_mooremachine#onState(state, cb)` | ||
Runs a callback on the next time that the FSM enters a given state or any | ||
sub-state of it. | ||
Parameters: | ||
- `state`: String, state to test for | ||
- `cb`: Function `(newState)` | ||
### `mod_mooremachine#gotoState(state)` | ||
Causes the FSM to enter the given new state. | ||
Parameters: | ||
- `state`: String, state to enter | ||
### `mod_mooremachine.FSM.wrap(fun)` | ||
Wraps a conventional node callback function up into an EventEmitter, to make | ||
life a little easier with `on()`. | ||
Parameters: | ||
- `fun`: Function `(cb)` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
14552
202
216