Skip to content

Commit fc28a25

Browse files
committed
ensure hooks are called in correct order even when transition is asynchronous (onbeforeevent, onleavestate, onenterstate, onafterevent)
1 parent f2e08bd commit fc28a25

4 files changed

Lines changed: 63 additions & 10 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ Something like
88

99
fsm.onleavemenu = function() {
1010
$('menu').fade(function() {
11-
fsm.next();
11+
fsm.transition();
1212
});
1313
}
1414

1515
fsm.onentergame = function() {
16-
// this doesn't get called until fsm.next() is called when the menu has finished fading
16+
// this doesn't get called until fsm.transition() is called when the menu has finished fading
1717
}
1818

1919
Or.... something else ! Have to wait and see how it pans out (without breaking existing synchronous behavior)

RELEASE_NOTES.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
Version 1.3.0 (unreleased)
1+
2+
Version 2.0.0 (unreleased)
23
--------------------------
34
* adding support for `async` event transitions (see README)
45
* added a generic `onchangestate(from,to)` hook to detect all state changes with a single callback.

state-machine.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,11 @@ StateMachine = {
7676
func.apply(this, args);
7777
},
7878

79-
transition: function(from, to, args) {
79+
transition: function(name, from, to, args) {
8080
this.current = to;
8181
StateMachine.enterState.call(this, to, args);
8282
StateMachine.changeState.call(this, from, to, args);
83+
StateMachine.afterEvent.call(this, name, args);
8384
},
8485

8586
buildEvent: function(name, map) {
@@ -97,20 +98,17 @@ StateMachine = {
9798
var self = this;
9899
var args = Array.prototype.slice.call(arguments);
99100

100-
if (StateMachine.beforeEvent.call(this, name, args) === false)
101-
return;
102-
103101
if (this.current != to) {
104-
this.transition = function() { StateMachine.transition.call(self, from, to, args); self.transition = null; };
102+
if (StateMachine.beforeEvent.call(this, name, args) === false)
103+
return;
104+
this.transition = function() { StateMachine.transition.call(self, name, from, to, args); self.transition = null; };
105105
this.transition.from = from;
106106
this.transition.to = to;
107107
StateMachine.exitState.call(this, this.current, arguments);
108108
if (!async && this.transition) // if not async OR user already called transition method (e.g. in an onleavestate hook)
109109
this.transition();
110110
}
111111

112-
StateMachine.afterEvent.call(this, name, arguments);
113-
114112
};
115113
}
116114

test/test_async.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,57 @@ test("state transition fired without completing previous transition", function()
157157

158158
//-----------------------------------------------------------------------------
159159

160+
test("hooks are called when appropriate", function() {
161+
162+
var fsm = StateMachine.create({
163+
initial: 'green',
164+
events: [
165+
{ name: 'warn', from: 'green', to: 'yellow', async: true },
166+
{ name: 'panic', from: 'yellow', to: 'red', async: true },
167+
{ name: 'calm', from: 'red', to: 'yellow', async: true },
168+
{ name: 'clear', from: 'yellow', to: 'green', async: true },
169+
]});
170+
171+
var called = [];
172+
173+
// generic state hook
174+
fsm.onchangestate = function(from,to) { called.push('onchange from ' + from + ' to ' + to); };
175+
176+
// state hooks
177+
fsm.onentergreen = function() { called.push('onentergreen'); };
178+
fsm.onleavegreen = function() { called.push('onleavegreen'); };
179+
fsm.onenteryellow = function() { called.push('onenteryellow'); };
180+
fsm.onleaveyellow = function() { called.push('onleaveyellow'); };
181+
fsm.onenterred = function() { called.push('onenterred'); };
182+
fsm.onleavered = function() { called.push('onleavered'); };
183+
184+
// event hooks
185+
fsm.onbeforewarn = function() { called.push('onbeforewarn'); };
186+
fsm.onafterwarn = function() { called.push('onafterwarn'); };
187+
fsm.onbeforepanic = function() { called.push('onbeforepanic'); };
188+
fsm.onafterpanic = function() { called.push('onafterpanic'); };
189+
fsm.onbeforecalm = function() { called.push('onbeforecalm'); };
190+
fsm.onaftercalm = function() { called.push('onaftercalm'); };
191+
fsm.onbeforeclear = function() { called.push('onbeforeclear'); };
192+
fsm.onafterclear = function() { called.push('onafterclear'); };
193+
194+
called = [];
195+
fsm.warn(); deepEqual(called, ['onbeforewarn', 'onleavegreen']);
196+
fsm.transition(); deepEqual(called, ['onbeforewarn', 'onleavegreen', 'onenteryellow', 'onchange from green to yellow', 'onafterwarn']);
197+
198+
called = [];
199+
fsm.panic(); deepEqual(called, ['onbeforepanic', 'onleaveyellow']);
200+
fsm.transition(); deepEqual(called, ['onbeforepanic', 'onleaveyellow', 'onenterred', 'onchange from yellow to red', 'onafterpanic']);
201+
202+
called = [];
203+
fsm.calm(); deepEqual(called, ['onbeforecalm', 'onleavered']);
204+
fsm.transition(); deepEqual(called, ['onbeforecalm', 'onleavered', 'onenteryellow', 'onchange from red to yellow', 'onaftercalm']);
205+
206+
called = [];
207+
fsm.clear(); deepEqual(called, ['onbeforeclear', 'onleaveyellow']);
208+
fsm.transition(); deepEqual(called, ['onbeforeclear', 'onleaveyellow', 'onentergreen', 'onchange from yellow to green', 'onafterclear']);
209+
210+
});
211+
212+
//-----------------------------------------------------------------------------
213+

0 commit comments

Comments
 (0)