From b44b92a32da56333a99ed735b8a8d27982046d87 Mon Sep 17 00:00:00 2001 From: Max Cahill Date: Mon, 5 Jul 2021 14:53:55 +1000 Subject: [PATCH] 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 --- state_machine.lua | 68 ++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/state_machine.lua b/state_machine.lua index 8ea1434..587700e 100644 --- a/state_machine.lua +++ b/state_machine.lua @@ -7,8 +7,8 @@ - a table with enter, exit, update and draw callbacks (all optional) which each take the state table and varargs as arguments - - a function - which gets passed the current event name, the machine, and varargs as arguments + - a plain function + 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 then the incoming state's enter callback is called @@ -27,11 +27,11 @@ local path = (...):gsub("state_machine", "") local class = require(path .. "class") local state_machine = class() -function state_machine:new(states, start) +function state_machine:new(states, start_in_state) self = self:init({ states = states or {}, - current_state = "", - start_state = start, + current_state_name = "", + reset_state_name = start_in_state or "", }) self:reset() @@ -39,16 +39,17 @@ function state_machine:new(states, start) return self 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 -function state_machine:_get_state() - return self.states[self.current_state] -end - --make an internal call function state_machine:_call(name, ...) - local state = self:_get_state() + local state = self:current_state() if state then if type(state[name]) == "function" then return state[name](state, ...) @@ -60,12 +61,12 @@ function state_machine:_call(name, ...) end --make an internal call --- return the call result if it isn't a valid state --- transition if the return value is a valid state - and return nil if so +-- transition if the return value is a valid state name - and return nil if so +-- return the call result if it isn't a valid state name function state_machine:_call_and_transition(name, ...) local r = self:_call(name, ...) 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 end return r @@ -75,7 +76,7 @@ end --various checks function state_machine:in_state(name) - return self.current_state == name + return self.current_state_name == name end function state_machine:has_state(name) @@ -86,13 +87,13 @@ end --state management --add a state -function state_machine:add_state(name, data) +function state_machine:add_state(name, state) if self:has_state(name) then error("error: added duplicate state "..name) else - self.states[name] = data + self.states[name] = state if self:in_state(name) then - self:_call("enter", self) + self:_call_and_transition("enter") end end @@ -102,7 +103,7 @@ end --remove a state function state_machine:remove_state(name) if not self:has_state(name) then - error("error: removed missed state "..name) + error("error: removed missing state "..name) else if self:in_state(name) then self:_call("exit") @@ -114,15 +115,16 @@ function state_machine:remove_state(name) end --hard-replace a state table ---if do_transitions is truthy and we're replacing the current 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) - local current = self:in_state(name) - if do_transitions and current then +-- if we're replacing the current state, +-- exit is called on the old state and enter is called on the new state +-- mask_transitions can be used to prevent this if you need to +function state_machine:replace_state(name, state, mask_transitions) + local do_transitions = not mask_transitions and self:in_state(name) + if do_transitions then self:_call("exit") end - self.states[name] = data - if do_transitions and current then + self.states[name] = state + if do_transitions then self:_call_and_transition("enter", self) end @@ -131,16 +133,16 @@ end --ensure a state doesn't exist; transition out of it if we're currently in it function state_machine:clear_state(name) - return self:replace_state(name, nil, true) + return self:replace_state(name, nil) end ------------------------------------------------------------------------------- --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() - if self.start_state then - self:set_state(self.start_state, true) + if self.reset_state_name then + self:set_state(self.reset_state_name, true) end end @@ -148,10 +150,10 @@ end -- if the enter callback of the target state returns a valid state name, -- then it is transitioned to in turn, -- and so on until the machine is at rest -function state_machine:set_state(state, reset) - if self.current_state ~= state or reset then +function state_machine:set_state(name, reset) + if self.current_state_name ~= name or reset then self:_call("exit") - self.current_state = state + self.current_state_name = name self:_call_and_transition("enter", self) end return self @@ -172,7 +174,7 @@ function state_machine:draw() self:_call("draw") 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) self.parent = parent self:reset()