refactored state machine to be clear about which variables and parameters are state tables, and which are name strings

breaking - changed some property and function names

fixes #16
This commit is contained in:
Max Cahill 2021-07-05 14:53:55 +10:00
parent 1de942daa8
commit b44b92a32d

View File

@ -7,8 +7,8 @@
- a table with enter, exit, update and draw callbacks (all optional) - a table with enter, exit, update and draw callbacks (all optional)
which each take the state table and varargs as arguments which each take the state table and varargs as arguments
- a function - a plain function
which gets passed the current event name, the machine, and varargs as arguments which gets passed the current event name, the machine table, and varargs as arguments
on changing state, the outgoing state's exit callback is called on changing state, the outgoing state's exit callback is called
then the incoming state's enter callback is called then the incoming state's enter callback is called
@ -27,11 +27,11 @@ local path = (...):gsub("state_machine", "")
local class = require(path .. "class") local class = require(path .. "class")
local state_machine = class() local state_machine = class()
function state_machine:new(states, start) function state_machine:new(states, start_in_state)
self = self:init({ self = self:init({
states = states or {}, states = states or {},
current_state = "", current_state_name = "",
start_state = start, reset_state_name = start_in_state or "",
}) })
self:reset() self:reset()
@ -39,16 +39,17 @@ function state_machine:new(states, start)
return self return self
end end
--get the current state table (or nil if it doesn't exist)
function state_machine:current_state()
return self.states[self.current_state_name]
end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
--internal helpers --internal helpers
function state_machine:_get_state()
return self.states[self.current_state]
end
--make an internal call --make an internal call
function state_machine:_call(name, ...) function state_machine:_call(name, ...)
local state = self:_get_state() local state = self:current_state()
if state then if state then
if type(state[name]) == "function" then if type(state[name]) == "function" then
return state[name](state, ...) return state[name](state, ...)
@ -60,12 +61,12 @@ function state_machine:_call(name, ...)
end end
--make an internal call --make an internal call
-- return the call result if it isn't a valid state -- transition if the return value is a valid state name - and return nil if so
-- transition if the return value is a valid state - and return nil if so -- return the call result if it isn't a valid state name
function state_machine:_call_and_transition(name, ...) function state_machine:_call_and_transition(name, ...)
local r = self:_call(name, ...) local r = self:_call(name, ...)
if type(r) == "string" and self:has_state(r) then if type(r) == "string" and self:has_state(r) then
self:set_state(r, r == self.current_state) self:set_state(r, r == self.current_state_name)
return nil return nil
end end
return r return r
@ -75,7 +76,7 @@ end
--various checks --various checks
function state_machine:in_state(name) function state_machine:in_state(name)
return self.current_state == name return self.current_state_name == name
end end
function state_machine:has_state(name) function state_machine:has_state(name)
@ -86,13 +87,13 @@ end
--state management --state management
--add a state --add a state
function state_machine:add_state(name, data) function state_machine:add_state(name, state)
if self:has_state(name) then if self:has_state(name) then
error("error: added duplicate state "..name) error("error: added duplicate state "..name)
else else
self.states[name] = data self.states[name] = state
if self:in_state(name) then if self:in_state(name) then
self:_call("enter", self) self:_call_and_transition("enter")
end end
end end
@ -102,7 +103,7 @@ end
--remove a state --remove a state
function state_machine:remove_state(name) function state_machine:remove_state(name)
if not self:has_state(name) then if not self:has_state(name) then
error("error: removed missed state "..name) error("error: removed missing state "..name)
else else
if self:in_state(name) then if self:in_state(name) then
self:_call("exit") self:_call("exit")
@ -114,15 +115,16 @@ function state_machine:remove_state(name)
end end
--hard-replace a state table --hard-replace a state table
--if do_transitions is truthy and we're replacing the current state, -- if we're replacing the current state,
--exit is called on the old state and enter is called on the new state -- exit is called on the old state and enter is called on the new state
function state_machine:replace_state(name, data, do_transitions) -- mask_transitions can be used to prevent this if you need to
local current = self:in_state(name) function state_machine:replace_state(name, state, mask_transitions)
if do_transitions and current then local do_transitions = not mask_transitions and self:in_state(name)
if do_transitions then
self:_call("exit") self:_call("exit")
end end
self.states[name] = data self.states[name] = state
if do_transitions and current then if do_transitions then
self:_call_and_transition("enter", self) self:_call_and_transition("enter", self)
end end
@ -131,16 +133,16 @@ end
--ensure a state doesn't exist; transition out of it if we're currently in it --ensure a state doesn't exist; transition out of it if we're currently in it
function state_machine:clear_state(name) function state_machine:clear_state(name)
return self:replace_state(name, nil, true) return self:replace_state(name, nil)
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
--transitions and updates --transitions and updates
--reset the machine to whatever the start state was defined at at creation --reset the machine state to whatever state was specified at creation
function state_machine:reset() function state_machine:reset()
if self.start_state then if self.reset_state_name then
self:set_state(self.start_state, true) self:set_state(self.reset_state_name, true)
end end
end end
@ -148,10 +150,10 @@ end
-- if the enter callback of the target state returns a valid state name, -- if the enter callback of the target state returns a valid state name,
-- then it is transitioned to in turn, -- then it is transitioned to in turn,
-- and so on until the machine is at rest -- and so on until the machine is at rest
function state_machine:set_state(state, reset) function state_machine:set_state(name, reset)
if self.current_state ~= state or reset then if self.current_state_name ~= name or reset then
self:_call("exit") self:_call("exit")
self.current_state = state self.current_state_name = name
self:_call_and_transition("enter", self) self:_call_and_transition("enter", self)
end end
return self return self
@ -172,7 +174,7 @@ function state_machine:draw()
self:_call("draw") self:_call("draw")
end end
--for compatibility when used as a state --for compatibility when a state machine is nested as a state in another machine
function state_machine:enter(parent) function state_machine:enter(parent)
self.parent = parent self.parent = parent
self:reset() self:reset()