1
0
mirror of https://github.com/Refactorio/RedMew.git synced 2024-12-12 10:04:40 +02:00
RedMew/utils/state_machine.lua
2019-02-18 01:43:59 -05:00

120 lines
4.6 KiB
Lua

--- This module provides a classical mealy/moore state machine.
-- Each machine in constructed by calling new()
-- States and Transitions are lazily added to the machine as transition handlers and state tick handlers are registered.
-- However the state machine must be fully defined after init is done. Dynamic machine changes are currently unsupported
-- An example usage can be found here: map_gen\combined\tetris\control.lua
local Module = {}
local Debug = require 'utils.debug'
local in_state_callbacks = {}
local transaction_callbacks = {}
local max_stack_depth = 20
local machine_count = 0
local control_stage = _STAGE.control
--- Transitions the supplied machine into a given state and executes all transaction_callbacks
-- @param self StateMachine
-- @param new_state number/string The new state to transition to
function Module.transition(self, new_state)
Debug.print(string.format('Transitioning from state %d to state %d.', self.state, new_state))
local old_state = self.state
local stack_depth = self.stack_depth
self.stack_depth = stack_depth + 1
if stack_depth > max_stack_depth then
if _DEBUG then
error('[WARNING] Stack overflow at:' .. debug.traceback())
else
log('[WARNING] Stack overflow at:' .. debug.traceback())
end
end
local exit_callbacks = transaction_callbacks[self.id][old_state]
if exit_callbacks then
local entry_callbacks = exit_callbacks[new_state]
if entry_callbacks then
for i = 1, #entry_callbacks do
local callback = entry_callbacks[i]
if callback then
callback()
end
end
end
end
self.state = new_state
end
--- Is this machine in this state?
-- @param self StateMachine
-- @param state number/string
-- @return boolean
function Module.in_state(self, state)
return self.state == state
end
--- Invoke a machine tick. Will execute all in_state_callbacks of the given machine
-- @param self StateMachine the machine, whose handlers will be invoked
function Module.machine_tick(self)
local callbacks = in_state_callbacks[self.id][self.state]
if callbacks then
for i=1, #callbacks do
local callback = callbacks[i]
if callback then
callback()
end
end
end
self.stack_depth = 0
end
--- Register a handler that will be invoked by StateMachine.machine_tick
-- You may register multiple handlers for the same transition
-- NOTICE: This function will invoke an error if called after init. Dynamic machine changes are currently unsupported
-- @param self StateMachine the machine
-- @param state number/string The state, that the machine will be in, when callback is invoked
-- @param callback function
function Module.register_state_tick_callback(self, state, callback)
if _LIFECYCLE ~= control_stage then
error('Calling StateMachine.register_state_tick_callback after the control stage is unsupported due to desyncs.', 2)
end
in_state_callbacks[self.id][state] = in_state_callbacks[self.id][state] or {}
table.insert(in_state_callbacks[self.id][state], callback)
end
--- Register a handler that will be invoked by StateMachine.transition
-- You may register multiple handlers for the same transition
-- NOTICE: This function will invoke an error if called after init. Dynamic machine changes are currently unsupported
-- @param self StateMachine the machine
-- @param state number/string exiting state
-- @param state number/string entering state
-- @param callback function
function Module.register_transition_callback(self, old, new, callback)
if _LIFECYCLE ~= control_stage then
error('Calling StateMachine.register_transition_callback after the control stage is unsupported due to desyncs.', 2)
end
transaction_callbacks[self.id][old] = transaction_callbacks[self.id][old] or {}
transaction_callbacks[self.id][old][new] = transaction_callbacks[self.id][old][new] or {}
table.insert(transaction_callbacks[self.id][old][new], callback)
end
--- Constructs a new state machine
-- @param init_state number/string The starting state of the machine
-- @return StateMachine The constructed state machine object
function Module.new(init_state)
if _LIFECYCLE ~= control_stage then
error('Calling StateMachine.new after the control stage is unsupported due to desyncs.', 2)
end
machine_count = machine_count + 1
in_state_callbacks[machine_count] = {}
transaction_callbacks[machine_count] = {}
return {
state = init_state,
stack_depth = 0,
id = machine_count,
}
end
return Module