Skip to content

Commit 9bf9521

Browse files
committed
Merge pull request #10 from kyleconroy/callbacks
Add callback support
2 parents 7390e82 + da705de commit 9bf9521

3 files changed

Lines changed: 32 additions & 105 deletions

File tree

README.md

Lines changed: 0 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -204,42 +204,6 @@ local fsm = machine.create({
204204

205205
>> _NOTE: If you decide to cancel the ASYNC event, you can call `fsm.transition.cancel()`
206206
207-
State Machine Classes
208-
=====================
209-
210-
You can also turn all instances of a _class_ into an FSM by applying
211-
the state machine functionality to the prototype, including your callbacks
212-
in your prototype, and providing a `startup` event for use when constructing
213-
instances:
214-
215-
```lua
216-
local machine = require('statemachine')
217-
local MyFSM = {}
218-
219-
function MyFSM:onpanic(event, from, to)
220-
print('panic')
221-
end
222-
223-
function MyFSM:onclear(event, from, to)
224-
print('all is clear')
225-
end
226-
227-
local fsm = machine.create({
228-
metatable = MyFSM,
229-
events = [
230-
{ name = 'startup', from = 'none', to = 'green' },
231-
{ name = 'warn', from = 'green', to = 'yellow' },
232-
{ name = 'panic', from = 'yellow', to = 'red' },
233-
{ name = 'calm', from = 'red', to = 'yellow' },
234-
{ name = 'clear', from = 'yellow', to = 'green' }
235-
}})
236-
```
237-
238-
This should be easy to adjust to fit your appropriate mechanism for object construction.
239-
240-
>> _NOTE: the `startup` event can be given any name, but it must be present in some form to
241-
ensure that each instance constructed is initialized with its own unique `current` state._
242-
243207
Initialization Options
244208
======================
245209

@@ -278,69 +242,3 @@ local fsm = machine.create({
278242
}})
279243
print(fsm.current) -- "green"
280244
```
281-
282-
If your object already has a `startup` method you can use a different name for the initial event
283-
284-
```lua
285-
local machine = require('statemachine')
286-
287-
local fsm = machine.create({
288-
inital = { state = 'green', event = 'init' },
289-
events = {
290-
{ name = 'panic', from = 'green', to = 'red' },
291-
{ name = 'calm', from = 'red', to = 'green' },
292-
}})
293-
print(fsm.current) -- "green"
294-
```
295-
296-
Finally, if you want to wait to call the initial state transition event until a later date you
297-
can `defer` it:
298-
299-
```lua
300-
local machine = require('statemachine')
301-
302-
local fsm = machine.create({
303-
inital = { state = 'green', event = 'init', defer = true},
304-
events = {
305-
{ name = 'panic', from = 'green', to = 'red' },
306-
{ name = 'calm', from = 'red', to = 'green' },
307-
}})
308-
309-
print(fsm.current) -- "none"
310-
fsm.init()
311-
print(fsm.current) -- "green"
312-
```
313-
314-
Of course, we have now come full circle, this last example is pretty much functionally the
315-
same as the first example in this section where you simply define your own startup event.
316-
317-
So you have a number of choices available to you when initializing your state machine.
318-
319-
>> _IMPORTANT NOTE: if you are using the pattern described in the previous section "State Machine
320-
Classes", and wish to declare an `initial` state in this manner, you MUST use the `defer: true`
321-
attribute and manually call the starting event in your constructor function. This will ensure
322-
that each instance gets its own unique `current` state, rather than an (unwanted) shared
323-
`current` state on the prototype object itself._
324-
325-
Handling Failures
326-
======================
327-
328-
By default, if you try to call an event method that is not allowed in the current state, the
329-
state machine will throw an exception. If you prefer to handle the problem yourself, you can
330-
define a custom `error` handler:
331-
332-
```lua
333-
local machine = require('statemachine')
334-
335-
local fsm = machine.create({
336-
inital = { state = 'green', event = 'init', defer = true},
337-
error = function(eventName, from, to, args, errorCode, errorMessage)
338-
return 'event ' .. eventName .. ' was naughty :- ' .. errorMessage
339-
end,
340-
events = {
341-
{ name = 'panic', from = 'green', to = 'red' },
342-
{ name = 'calm', from = 'red', to = 'green' },
343-
}})
344-
345-
print(fsm:calm()); -- "event calm was naughty :- event not allowed in current state green"
346-
```

spec/fsm_spec.lua

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,32 @@ describe("Lua state machine framework", function()
5151
assert.is_false(fsm:is('yellow'))
5252
end)
5353

54+
it("should fire callbacks", function()
55+
local fsm = machine.create({
56+
initial = 'green',
57+
events = stoplight,
58+
callbacks = {
59+
onbeforewarn = stub.new(),
60+
onleavegreen = stub.new(),
61+
onenteryellow = stub.new(),
62+
onafterwarn = stub.new(),
63+
onstatechange = stub.new(),
64+
onyellow = stub.new(),
65+
onwarn = stub.new()
66+
}
67+
})
68+
69+
fsm:warn()
70+
71+
assert.spy(fsm.onbeforewarn).was_called_with(fsm, 'warn', 'green', 'yellow')
72+
assert.spy(fsm.onleavegreen).was_called_with(fsm, 'warn', 'green', 'yellow')
73+
assert.spy(fsm.onenteryellow).was_called_with(fsm, 'warn', 'green', 'yellow')
74+
assert.spy(fsm.onafterwarn).was_called_with(fsm, 'warn', 'green', 'yellow')
75+
assert.spy(fsm.onstatechange).was_called_with(fsm, 'warn', 'green', 'yellow')
76+
assert.spy(fsm.onyellow).was_not_called()
77+
assert.spy(fsm.onwarn).was_not_called()
78+
end)
79+
5480
it("should fire handlers", function()
5581
fsm.onbeforewarn = stub.new()
5682
fsm.onleavegreen = stub.new()
@@ -73,7 +99,6 @@ describe("Lua state machine framework", function()
7399
assert.spy(fsm.onwarn).was_not_called()
74100
end)
75101

76-
77102
it("should accept additional arguments to handlers", function()
78103
fsm.onbeforewarn = stub.new()
79104
fsm.onleavegreen = stub.new()

statemachine.lua

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,20 @@ function machine.create(options)
5050
local fsm = {}
5151
setmetatable(fsm, machine)
5252

53+
fsm.options = options
5354
fsm.current = options.initial or 'none'
5455
fsm.events = {}
55-
fsm.options = options
5656

57-
for _, event in ipairs(options.events) do
57+
for _, event in ipairs(options.events or {}) do
5858
local name = event.name
5959
fsm[name] = fsm[name] or create_transition(name)
6060
fsm.events[name] = fsm.events[name] or { map = {} }
6161
add_to_map(fsm.events[name].map, event)
6262
end
63+
64+
for name, callback in pairs(options.callbacks or {}) do
65+
fsm[name] = callback
66+
end
6367

6468
return fsm
6569
end

0 commit comments

Comments
 (0)