Created
August 23, 2012 15:27
-
-
Save egonelbre/3437746 to your computer and use it in GitHub Desktop.
State Machine
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Machine(first, $){ | |
var cur = {}, next = $[first]; | |
var self = { | |
go : function(to){ next = next ? next : ($[to] || $.undefined); }, | |
trigger : function(event){ var t = cur.tx && cur.tx[event]; t && self.go(t); } | |
}; | |
return function(){ | |
if(next){ | |
cur.exit && cur.exit.call(self); | |
cur = next; next = undefined; | |
self.__proto__ = cur.data; | |
cur.enter && cur.enter.call(self); | |
} | |
cur.fn && cur.fn.call(self); | |
}; | |
}; | |
var m = Machine("tick", { | |
undefined : { | |
fn : function(){ | |
console.log("ERROR: NO STATE!"); | |
this.trigger("recover"); | |
}, | |
tx : { | |
recover : "tick" | |
} | |
}, | |
tick : { | |
enter : function(){ | |
console.log("entered tick"); | |
}, | |
fn : function(){ | |
console.log("tick"); | |
this.go("tock"); | |
} | |
}, | |
tock : { | |
data : { | |
counter : 0 | |
}, | |
exit : function(){ | |
this.counter += 1; | |
}, | |
fn : function(){ | |
this.counter += 1; | |
console.log("tock : " + this.counter); | |
if(this.counter > 5) | |
this.go("invalid"); | |
if(this.counter > 3) | |
this.trigger("forward"); | |
}, | |
tx : { | |
forward : "tick" | |
} | |
} | |
}); | |
console.log("========================="); | |
for(var i = 0; i < 15; i += 1) | |
m(); |
I wouldn't recommend writing this convoluted code, but the short explanation is:
$
inMachine
keeps all the different states.- line 7 returns a function that advances the state machine
- stepping the state machine involves several steps:
3.1 check whether we need to go to a different state, if yes:
3.1.1 then call the exit method on the state
3.1.2 then change the state
3.1.3 change theself
data to point to the current state
3.1.4. call the enter on the state
3.2. call the state function
self
acts the object for holding the state and different methods. The methods are go
, which changes the state directly to another. The other one is trigger
which looks up the event name from tx
and then advances to that state.
Changing the data
that this
has is done via changing out the ._proto_
inheritance.
Here's a slightly simpler version of it:
function Machine(first, states){
return {
currentState: undefined,
nextState: states[first],
states: states,
go(targetState){
// the first `go` wins
if(this.nextState !== undefined) {
return
}
this.nextState = this.states[targetState];
if(this.nextState === undefined) {
// fallback to an error state
this.nextState = this.states.undefined;
}
},
advance() {
if(this.nextState) {
this.currentState = this.nextState;
this.nextState = undefined;
this.__proto__ = this.currentState.data;
}
if(this.currentState.fn) {
this.currentState.fn.call(this);
}
}
}
};
var machine = Machine("tick", {
tick : {
fn : function(){
console.log("tick");
this.go("tock");
}
},
tock : {
data : {
counter : 0
},
fn : function(){
this.counter += 1;
console.log("tock : " + this.counter);
if(this.counter > 5)
this.go("tick");
}
}
});
console.log("=========================");
for(var i = 0; i < 15; i += 1)
machine.advance();
Thanks, bro.
I tried to write a typesafe version which is written in Typescript.
This is the transpiled result:
var tsCustomError = require('ts-custom-error');
class StateMachine {
_currentState;
_states;
_logic;
constructor(states, initialState, logic) {
this._states = states;
this._currentState = initialState;
this._logic = logic;
}
getAllStates() {
return [...this._states];
}
getCurrentState() {
return this._currentState;
}
_verifyState(value) {
if (!this._states.includes(value)) {
throw new tsCustomError.CustomError(`[StateMachine][_verifyState]: value "${value}" is not defined in initial states!`);
}
}
switchBy(...factors) {
const newState = this._logic(this._currentState, ...factors);
this._verifyState(newState);
this._currentState = newState;
return this._currentState;
}
}
exports.StateMachine = StateMachine;
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey, bro, I couldn't understand the logic of your code even with debugging. It's harder to understand in a closure style. Do you have an article to explain it?