-
Notifications
You must be signed in to change notification settings - Fork 66
/
statemachine.lua
157 lines (126 loc) · 3.7 KB
/
statemachine.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
local machine = {}
machine.__index = machine
local NONE = "none"
local ASYNC = "async"
local function call_handler(handler, params)
if handler then
return handler(unpack(params))
end
end
local function create_transition(name)
local can, to, from, params
local function transition(self, ...)
if self.asyncState == NONE then
can, to = self:can(name)
from = self.current
params = { self, name, from, to, ...}
if not can then return false end
self.currentTransitioningEvent = name
local beforeReturn = call_handler(self["onbefore" .. name], params)
local leaveReturn = call_handler(self["onleave" .. from], params)
if beforeReturn == false or leaveReturn == false then
return false
end
self.asyncState = name .. "WaitingOnLeave"
if leaveReturn ~= ASYNC then
transition(self, ...)
end
return true
elseif self.asyncState == name .. "WaitingOnLeave" then
self.current = to
local enterReturn = call_handler(self["onenter" .. to] or self["on" .. to], params)
self.asyncState = name .. "WaitingOnEnter"
if enterReturn ~= ASYNC then
transition(self, ...)
end
return true
elseif self.asyncState == name .. "WaitingOnEnter" then
call_handler(self["onafter" .. name] or self["on" .. name], params)
call_handler(self["onstatechange"], params)
self.asyncState = NONE
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
end
self.currentTransitioningEvent = nil
return false
end
return transition
end
local function add_to_map(map, event)
if type(event.from) == 'string' then
map[event.from] = event.to
else
for _, from in ipairs(event.from) do
map[from] = event.to
end
end
end
function machine.create(options)
assert(options.events)
local fsm = {}
setmetatable(fsm, machine)
fsm.options = options
fsm.current = options.initial or 'none'
fsm.asyncState = NONE
fsm.events = {}
for _, event in ipairs(options.events or {}) do
local name = event.name
fsm[name] = fsm[name] or create_transition(name)
fsm.events[name] = fsm.events[name] or { map = {} }
add_to_map(fsm.events[name].map, event)
end
for name, callback in pairs(options.callbacks or {}) do
fsm[name] = callback
end
return fsm
end
function machine:is(state)
return self.current == state
end
function machine:can(e)
local event = self.events[e]
local to = event and event.map[self.current] or event.map['*']
return to ~= nil, to
end
function machine:cannot(e)
return not self:can(e)
end
function machine:todot(filename)
local dotfile = io.open(filename,'w')
assert(dotfile~=nil)
dotfile:write('digraph {\n')
local transition = function(event,from,to)
dotfile:write(string.format('%s -> %s [label=%s];\n',from,to,event))
end
for _, event in pairs(self.options.events) do
if type(event.from) == 'table' then
for _, from in ipairs(event.from) do
transition(event.name,from,event.to)
end
else
transition(event.name,event.from,event.to)
end
end
dotfile:write('}\n')
dotfile:close()
end
function machine:transition(event)
if self.currentTransitioningEvent == event then
return self[self.currentTransitioningEvent](self)
end
end
function machine:cancelTransition(event)
if self.currentTransitioningEvent == event then
self.asyncState = NONE
self.currentTransitioningEvent = nil
end
end
machine.NONE = NONE
machine.ASYNC = ASYNC
return machine