Skip to content

Commit

Permalink
WIP: work on transitions and guards
Browse files Browse the repository at this point in the history
  • Loading branch information
kernow committed Apr 15, 2009
1 parent 0f126aa commit f17e04c
Show file tree
Hide file tree
Showing 13 changed files with 215 additions and 48 deletions.
40 changes: 34 additions & 6 deletions lib/event.js
@@ -1,14 +1,42 @@

SM.Event = function(name, options) {
console.log("******* creating a new event *********");
this.name = name;
this.options = options;
SM.Event = function(name, machine, options) {
this.name = name;
this.machine = machine;
this.options = options;
this.guards = new SM.GuardsCollection();
return this;
}

SM.Event.prototype = {
transition: function(){
console.log("******* creating a new transition *********");
transition: function(options){
// var guard = new SM.Guard(this.name, options)
// this.guards.push(guard);
this.guards.add(this.name, options);
this.machine.states.add([options.from, options.to]);
return this;
},
can_fire: function(){
return this.guards.match(this.name, this.machine.state);
},
fire: function(){
console.log("******* fire called *********");
var transition = this.transition_for();
if( transition ){
// do the transition
transition.perform();
}
else{
// transition failed
}
},
transition_for: function(){
if(this.can_fire()){
var from = this.machine.state;
var to = this.guards.find_to_state(this.name, from);
return new SM.Transition(this.machine, this, from, to);
}
else{
return false;
}
}
}
8 changes: 2 additions & 6 deletions lib/event_collection.js
Expand Up @@ -5,13 +5,9 @@ SM.EventCollection = function(){
}

SM.EventCollection.prototype = {
// new: function(){
// this.events = [];
// return this;
// },
add: function(name, options) {
add: function(name, machine, options) {
// creates an event
var event = new SM.Event(name, options)
var event = new SM.Event(name, machine, options)
this.events.push(event);
return event;
},
Expand Down
18 changes: 18 additions & 0 deletions lib/guard.js
@@ -0,0 +1,18 @@

SM.Guard = function(name, options){
this.name = name;
this.from = options.from;
this.to = options.to;
return this;
}

SM.Guard.prototype = {
match: function(name, from){
if(name == this.name && from == this.from){
return true;
}
else{
return false;
}
}
}
27 changes: 27 additions & 0 deletions lib/guard_collection.js
@@ -0,0 +1,27 @@

SM.GuardsCollection = function() {
this.guards = [];
return this;
}

SM.GuardsCollection.prototype = {
add: function(name, options) {
var guard = new SM.Guard(name, options)
this.guards.push(guard);
return guard;
},
all: function() {
return this.guards;
},
match: function(name, from){
for(var i=0;i<this.guards.length;i++){
if( this.guards[i].match(name, from) ){
return true;
}
}
return false;
},
find_to_state: function(name, from){
return 'idling';
}
}
23 changes: 16 additions & 7 deletions lib/machine.js
@@ -1,7 +1,7 @@

SM.Machine = function(name, object, options) {
this.states = new SM.StateCollection();
this.events = new SM.EventCollection();
this.states = new SM.StateCollection();

this.state = options && options.initial ? options.initial : '';
this.add_methods_to_object(object, name);
Expand All @@ -12,18 +12,27 @@ SM.Machine.prototype = {

add_methods_to_object: function(object, name){
object.state_machine = this;
object[name] = this.state;
object[name] = function(){ return this.state_machine.get_state() };
object[name+'_events'] = this.events.all();
object[name+'_states'] = this.states.all();
object.event = this.event;
},
event: function(name, options) {
// creates an event
var event = this.state_machine.events.add(name, options);
this.state_machine.add_event_methods(name, this);
var event = this.state_machine.events.add(name, this.state_machine, options);
this.state_machine.add_event_methods(name, this, event);
return event;
},
add_event_methods: function(name, obj) {
obj[name] = function(){};
obj['can_'+name] = function(){};
add_event_methods: function(name, object, event) {
object[name] = function(){ return event.fire() };
object['can_'+name] = function(){ return event.can_fire() };
},
get_state: function(){
console.log("getting state: "+this.state);
return this.state;
},
set_state: function(state){
console.log("setting state to: "+state);
this.state = state;
}
};
9 changes: 9 additions & 0 deletions lib/state.js
@@ -0,0 +1,9 @@

SM.State = function(name) {
// console.log("******* creating a new state *********");
this.name = name;
return this;
}

SM.State.prototype = {
}
20 changes: 17 additions & 3 deletions lib/state_collection.js
@@ -1,11 +1,25 @@

SM.StateCollection = function() {
this.events = [];
this.states = [];
return this;
}

SM.StateCollection.prototype = {
add: function(name, options) {
add: function(name) {
// creates an event
}
// console.log(">>>>>>> adding state: "+name);
// console.log(typeof name);
if(typeof name == 'string'){
var state = new SM.State(name)
this.states.push(state);
}
else{
for(var i=0;i<name.length;i++){
this.add(name[i]);
}
}
},
all: function() {
return this.states;
}
}
22 changes: 14 additions & 8 deletions lib/transition.js
@@ -1,14 +1,20 @@

SM.Transition = function(){
SM.Transition = function(machine, event, from, to){
console.log("machine state: "+machine.state);
console.log("event: "+event);
console.log("from: "+from);
console.log("to: "+to);
this.machine = machine;
this.event = event;
this.from = from;
this.to = to;
return this;
}

SM.Transition.prototype = {
add: function(name, options) {
// creates an event
this.events.push({ name: name, options: options});
},
all: function() {
return this.events;
}
perform: function() {
console.log("machine state: "+this.machine.state);
// do the transition with all the before and after callbacks
this.machine.set_state(this.to);
}
}
7 changes: 6 additions & 1 deletion test/acceptance.html
Expand Up @@ -16,13 +16,18 @@
<script type="text/javascript" src="../lib/machine.js"></script>
<script type="text/javascript" src="../lib/state.js"></script>
<script type="text/javascript" src="../lib/event.js"></script>
<script type="text/javascript" src="../lib/event_collection.js"></script>
<script type="text/javascript" src="../lib/guard.js"></script>
<script type="text/javascript" src="../lib/transition.js"></script>
<script type="text/javascript" src="../lib/state_collection.js"></script>
<script type="text/javascript" src="../lib/event_collection.js"></script>
<script type="text/javascript" src="../lib/guard_collection.js"></script>


<!-- Test suit -->
<script type="text/javascript" src="js/test_runner.js"></script>
<script type="text/javascript" src="js/state_machine_tests.js"></script>
<script type="text/javascript" src="js/transition_tests.js"></script>
<script type="text/javascript" src="js/event_tests.js"></script>

</head>

Expand Down
45 changes: 45 additions & 0 deletions test/js/event_tests.js
@@ -0,0 +1,45 @@

jsStateMachineTests.EventTests = function(Y) {
var testSuite = new Y.Test.Suite("Event");

testSuite.add(new Y.Test.Case({

name: "general",

setUp : function () {
this.car = {};
},

tearDown : function () {
delete this.car;
},

testCanCheckIfEventCanBeFired : function () {
new SM.StateMachine('state', this.car, { initial: 'parked' });
this.car.event('start')
.transition({ from: 'parked', to: 'idling' });

this.car.event('stop')
.transition({ from: 'idling', to: 'parked' });

Y.Assert.isTrue(this.car.can_start());
Y.Assert.isFalse(this.car.can_stop());
},

testCanFireAndEvent : function () {
new SM.StateMachine('state', this.car, { initial: 'parked' });
this.car.event('start')
.transition({ from: 'parked', to: 'idling' });

this.car.event('stop')
.transition({ from: 'idling', to: 'parked' });

this.car.start();
Y.Assert.areEqual('idling', this.car.state());
// Y.Assert.isTrue(this.car.can_start());
// Y.Assert.isFalse(this.car.can_stop());
}
}));

return testSuite;
};
35 changes: 22 additions & 13 deletions test/js/state_machine_tests.js
Expand Up @@ -32,26 +32,35 @@ jsStateMachineTests.StateMachineTests = function(Y) {
},
testShouldSetTheInitialStateAsBlank : function () {
new SM.StateMachine('state', this.car);
Y.Assert.areEqual('', this.car.state);
Y.Assert.areEqual('', this.car.state());
},
testShouldSetTheInitialState : function () {
new SM.StateMachine('state', this.car, { initial: 'idle' });
Y.Assert.areEqual('idle', this.car.state);
new SM.StateMachine('state', this.car, { initial: 'parked' });
Y.Assert.areEqual('parked', this.car.state());
},
testShouldAddNewEventMethods : function () {
new SM.StateMachine('state', this.car, { initial: 'idle' });
new SM.StateMachine('state', this.car, { initial: 'parked' });
this.car.event('start');
Y.Assert.isFunction(this.car.start);
Y.Assert.isFunction(this.car.can_start);
},
testCanGetListOfEvents : function () {
new SM.StateMachine('state', this.car, { initial: 'idle' });
new SM.StateMachine('state', this.car, { initial: 'parked' });
this.car.event('start');
this.car.event('gear_up');
this.car.event('gear_down');
Y.Assert.isTrue(contains(this.car.state_events, 'start'));
Y.Assert.isTrue(contains(this.car.state_events, 'gear_up'));
Y.Assert.isTrue(contains(this.car.state_events, 'gear_down'));
},
testCanGetListOfStates : function () {
new SM.StateMachine('state', this.car, { initial: 'parked' });
this.car.event('start').transition({from: 'parked', to: 'idling'});
this.car.event('gear_up').transition({from: 'idling', to: 'first_gear'});
this.car.event('gear_down').transition({from: 'first_gear', to: 'idling'});
Y.Assert.isTrue(contains(this.car.state_states, 'parked'));
Y.Assert.isTrue(contains(this.car.state_states, 'idling'));
Y.Assert.isTrue(contains(this.car.state_states, 'first_gear'));
}
}));

Expand All @@ -70,17 +79,17 @@ jsStateMachineTests.StateMachineTests = function(Y) {
testShouldSetTheInitialStateAsBlank : function () {
new SM.StateMachine('state', this.car);
new SM.StateMachine('alarm_state', this.car);
Y.Assert.areEqual('', this.car.state);
Y.Assert.areEqual('', this.car.alarm_state);
Y.Assert.areEqual('', this.car.state());
Y.Assert.areEqual('', this.car.alarm_state());
},
testShouldSetTheInitialState : function () {
new SM.StateMachine('state', this.car, { initial: 'idle' });
new SM.StateMachine('state', this.car, { initial: 'parked' });
new SM.StateMachine('alarm_state', this.car, { initial: 'active' });
Y.Assert.areEqual('idle', this.car.state);
Y.Assert.areEqual('active', this.car.alarm_state);
Y.Assert.areEqual('parked', this.car.state());
Y.Assert.areEqual('active', this.car.alarm_state());
},
testShouldAddNewEventMethods : function () {
new SM.StateMachine('state', this.car, { initial: 'idle' });
new SM.StateMachine('state', this.car, { initial: 'parked' });
this.car.event('start');
new SM.StateMachine('alarm_state', this.car, { initial: 'active' });
this.car.event('disable');
Expand All @@ -90,11 +99,11 @@ jsStateMachineTests.StateMachineTests = function(Y) {
Y.Assert.isFunction(this.car.can_disable);
},
testCanGetListOfEvents : function () {
new SM.StateMachine('state', this.car, { initial: 'idle' });
new SM.StateMachine('state', this.car, { initial: 'parked' });
this.car.event('start');
this.car.event('gear_up');
this.car.event('gear_down');
new SM.StateMachine('alarm_state', this.car, { initial: 'idle' });
new SM.StateMachine('alarm_state', this.car, { initial: 'parked' });
this.car.event('enable');
this.car.event('disable');
this.car.event('alarm');
Expand Down
1 change: 1 addition & 0 deletions test/js/test_runner.js
Expand Up @@ -10,5 +10,6 @@ YUI({base: 'js/yui/build/'}).use("console", "yuitest", function(Y) {
yconsole.render('#testLogger');
Y.Test.Runner.add(jsStateMachineTests.StateMachineTests(Y));
Y.Test.Runner.add(jsStateMachineTests.TransitionTests(Y));
Y.Test.Runner.add(jsStateMachineTests.EventTests(Y));
Y.Test.Runner.run();
});
8 changes: 4 additions & 4 deletions test/js/transition_tests.js
Expand Up @@ -15,13 +15,13 @@ jsStateMachineTests.TransitionTests = function(Y) {
},

testCanAddTransition : function () {
new SM.StateMachine('state', this.car, { initial: 'idle' });
new SM.StateMachine('state', this.car, { initial: 'parked' });
Y.Assert.isObject( this.car.event('start')
.transition({ from:'idle', to:'running' })
.transition({ from:'parked', to:'idling' })
);
Y.Assert.isObject( this.car.event('start')
.transition({ from:'idle', to:'running' })
.transition({ from:'running', to:'idle' })
.transition({ from:'parked', to:'idling' })
.transition({ from:'idling', to:'parked' })
);
}
}));
Expand Down

0 comments on commit f17e04c

Please sign in to comment.