Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ local fsm = machine.create({
})
```

If you decide to cancel the async event, you can call `fsm.cancelTransition(eventName)`
If you decide to cancel the async event, you can call `fsm:cancelTransition(eventName)`

Initialization Options
======================
Expand All @@ -243,14 +243,14 @@ fsm:startup()
print(fsm.current) -- "green"
```

If you specify the name of your initial event (as in all the earlier examples), then an
implicit `startup` event will be created for you and fired when the state machine is constructed.
If you specify an initial state (as in all the earlier examples), the state machine is
constructed in that state. No transition callbacks are fired during construction.

```lua
local machine = require('statemachine')

local fsm = machine.create({
inital = 'green',
initial = 'green',
events = {
{ name = 'panic', from = 'green', to = 'red' },
{ name = 'calm', from = 'red', to = 'green' },
Expand Down
59 changes: 59 additions & 0 deletions spec/fsm_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ describe("Lua state machine framework", function()
assert.is_true(fsm:cannot('clear'))
end)

it("should reject unknown events", function()
assert.is_false(fsm:can('missing'))
assert.is_true(fsm:cannot('missing'))
end)

it("should support checking states", function()
assert.is_true(fsm:is('green'))
assert.is_false(fsm:is('red'))
Expand Down Expand Up @@ -139,17 +144,24 @@ describe("Lua state machine framework", function()

assert.is_false(result)
assert.are_equal(fsm.current, 'green')
assert.is_nil(fsm.currentTransitioningEvent)
end)

it("should cancel the warn event from onbeforewarn", function()
local leaves = 0
fsm.onbeforewarn = function(self, name, from, to)
return false
end
fsm.onleavegreen = function()
leaves = leaves + 1
end

local result = fsm:warn()

assert.is_false(result)
assert.are_equal(fsm.current, 'green')
assert.are_equal(leaves, 0)
assert.is_nil(fsm.currentTransitioningEvent)
end)

it("pauses when async is passed", function()
Expand Down Expand Up @@ -245,6 +257,53 @@ describe("Lua state machine framework", function()
assert.are_equal(fsm.current, 'red')
end)

it("should reject an invalid event during an async transition", function()
fsm.onleavegreen = function(self, name, from, to)
return fsm.ASYNC
end

assert.is_true(fsm:warn())
assert.is_false(fsm:panic())
assert.are_equal(fsm.current, 'green')
assert.are_equal(fsm.asyncState, fsm.NONE)
assert.is_nil(fsm.currentTransitioningEvent)
assert.is_nil(fsm:transition('warn'))
end)

it("should preserve nil arguments through async handlers", function()
local received = {}
local function capture(...)
return {
count = select('#', ...),
first = select(1, ...),
second = select(2, ...),
third = select(3, ...),
fourth = select(4, ...)
}
end

fsm.onleavegreen = function(self, name, from, to, ...)
received.leave = capture(...)
return fsm.ASYNC
end
fsm.onenteryellow = function(self, name, from, to, ...)
received.enter = capture(...)
end

assert.is_true(fsm:warn('one', nil, 'three', nil))
assert.is_true(fsm:transition('warn'))

assert.is_not_nil(received.leave)
assert.is_not_nil(received.enter)
for _, callback in pairs(received) do
assert.are_equal(4, callback.count)
assert.are_equal('one', callback.first)
assert.is_nil(callback.second)
assert.are_equal('three', callback.third)
assert.is_nil(callback.fourth)
end
end)

it("should properly cancel the transition if asked", function()
fsm.onleavegreen = function(self, name, from, to)
return fsm.ASYNC
Expand Down
23 changes: 15 additions & 8 deletions statemachine.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ local unpack = unpack or table.unpack

local function call_handler(handler, params)
if handler then
return handler(unpack(params))
return handler(unpack(params, 1, params.n))
end
end

Expand All @@ -21,14 +21,21 @@ local function create_transition(name)
can, to = self:can(name)
from = self.current
params = { self, name, from, to, ...}
params.n = 4 + select('#', ...)

if not can then return false end
self.currentTransitioningEvent = name

local beforeReturn = call_handler(self["onbefore" .. name], params)
if beforeReturn == false then
self.currentTransitioningEvent = nil
return false
end

local leaveReturn = call_handler(self["onleave" .. from], params)

if beforeReturn == false or leaveReturn == false then
if leaveReturn == false then
self.currentTransitioningEvent = nil
return false
end

Expand Down Expand Up @@ -58,11 +65,11 @@ local function create_transition(name)
self.currentTransitioningEvent = nil
return true
else
if string.find(self.asyncState, "WaitingOnLeave") or string.find(self.asyncState, "WaitingOnEnter") then
self.asyncState = NONE
transition(self, ...)
return true
end
if string.find(self.asyncState, "WaitingOnLeave") or string.find(self.asyncState, "WaitingOnEnter") then
self.asyncState = NONE
self.currentTransitioningEvent = nil
return transition(self, ...)
end
end

self.currentTransitioningEvent = nil
Expand Down Expand Up @@ -113,7 +120,7 @@ end

function machine:can(e)
local event = self.events[e]
local to = event and event.map[self.current] or event.map['*']
local to = event and (event.map[self.current] or event.map['*'])
return to ~= nil, to
end

Expand Down