initial commit
This commit is contained in:
182
LibTSM/Util/FSMClasses/Machine.lua
Normal file
182
LibTSM/Util/FSMClasses/Machine.lua
Normal file
@@ -0,0 +1,182 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- FSMMachine Class.
|
||||
-- This class allows implementing event-driving finite state machines.
|
||||
-- @classmod FSMMachine
|
||||
|
||||
local _, TSM = ...
|
||||
local Machine = TSM.Init("Util.FSMClasses.Machine")
|
||||
local State = TSM.Include("Util.FSMClasses.State")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local Log = TSM.Include("Util.Log")
|
||||
local LibTSMClass = TSM.Include("LibTSMClass")
|
||||
local FSMMachine = LibTSMClass.DefineClass("FSMMachine")
|
||||
local private = {
|
||||
eventTransitionHandlerCache = {},
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function Machine.Create(name)
|
||||
return FSMMachine(name)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Class Meta Methods
|
||||
-- ============================================================================
|
||||
|
||||
function FSMMachine.__init(self, name)
|
||||
self._name = name
|
||||
self._currentState = nil
|
||||
self._context = nil
|
||||
self._loggingDisabledCount = 0
|
||||
self._stateObjs = {}
|
||||
self._defaultEvents = {}
|
||||
self._handlingEvent = nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
--- Add an FSM state.
|
||||
-- @tparam FSM self The FSM object
|
||||
-- @tparam FSMState stateObj The FSM state object to add
|
||||
-- @treturn FSM The FSM object
|
||||
function FSMMachine.AddState(self, stateObj)
|
||||
assert(State.IsInstance(stateObj))
|
||||
local name = stateObj:_GetName()
|
||||
assert(not self._stateObjs[name], "state already exists")
|
||||
self._stateObjs[stateObj:_GetName()] = stateObj
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a default event handler.
|
||||
-- @tparam FSM self The FSM object
|
||||
-- @tparam string event The event name
|
||||
-- @tparam function handler The default event handler
|
||||
-- @treturn FSM The FSM object
|
||||
function FSMMachine.AddDefaultEvent(self, event, handler)
|
||||
assert(not self._defaultEvents[event], "event already exists")
|
||||
self._defaultEvents[event] = handler
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a simple default event-based transition.
|
||||
-- @tparam FSMMachine self The FSMMachine object
|
||||
-- @tparam string event The event name
|
||||
-- @tparam string toState The state to transition to
|
||||
-- @treturn FSMMachine The FSMMachine object
|
||||
function FSMMachine.AddDefaultEventTransition(self, event, toState)
|
||||
if not private.eventTransitionHandlerCache[toState] then
|
||||
private.eventTransitionHandlerCache[toState] = function(context, ...)
|
||||
return toState, ...
|
||||
end
|
||||
end
|
||||
return self:AddDefaultEvent(event, private.eventTransitionHandlerCache[toState])
|
||||
end
|
||||
|
||||
--- Initialize the FSM.
|
||||
-- @tparam FSM self The FSM object
|
||||
-- @tparam string initialState The name of the initial state
|
||||
-- @param[opt={}] context The FSM context table which gets passed to all state and event handlers
|
||||
-- @treturn FSM The FSM object
|
||||
function FSMMachine.Init(self, initialState, context)
|
||||
assert(self._stateObjs[initialState], "invalid initial state")
|
||||
self._currentState = initialState
|
||||
self._context = context or {}
|
||||
-- validate all the transitions
|
||||
for name, obj in pairs(self._stateObjs) do
|
||||
for _, toState in obj:_ToStateIterator() do
|
||||
assert(self._stateObjs[toState], format("toState doesn't exist (%s -> %s)", name, toState))
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Process an event.
|
||||
-- @tparam FSM self The FSM object
|
||||
-- @tparam string event The name of the event
|
||||
-- @tparam[opt] vararg ... Additional arguments to pass to the handler function
|
||||
-- @treturn FSM The FSM object
|
||||
function FSMMachine.ProcessEvent(self, event, ...)
|
||||
assert(self._currentState, "FSM not initialized")
|
||||
if self._handlingEvent then
|
||||
Log.RaiseStackLevel()
|
||||
Log.Warn("[%s] %s (ignored - handling event - %s)", self._name, event, self._handlingEvent)
|
||||
Log.LowerStackLevel()
|
||||
return self
|
||||
elseif self._inTransition then
|
||||
Log.RaiseStackLevel()
|
||||
Log.Warn("[%s] %s (ignored - in transition)", self._name, event)
|
||||
Log.LowerStackLevel()
|
||||
return self
|
||||
end
|
||||
|
||||
if self._loggingDisabledCount == 0 then
|
||||
Log.RaiseStackLevel()
|
||||
Log.Info("[%s] %s", self._name, event)
|
||||
Log.LowerStackLevel()
|
||||
end
|
||||
self._handlingEvent = event
|
||||
local currentStateObj = self._stateObjs[self._currentState]
|
||||
if currentStateObj:_HasEventHandler(event) then
|
||||
self:_Transition(TempTable.Acquire(currentStateObj:_ProcessEvent(event, self._context, ...)))
|
||||
elseif self._defaultEvents[event] then
|
||||
self:_Transition(TempTable.Acquire(self._defaultEvents[event](self._context, ...)))
|
||||
end
|
||||
self._handlingEvent = nil
|
||||
return self
|
||||
end
|
||||
|
||||
--- Enable or disable event and state transition logs (can be called recursively).
|
||||
-- @tparam FSM self The FSM object
|
||||
-- @tparam boolean enabled Whether or not logging should be enabled
|
||||
-- @treturn FSM The FSM object
|
||||
function FSMMachine.SetLoggingEnabled(self, enabled)
|
||||
self._loggingDisabledCount = self._loggingDisabledCount + (enabled and -1 or 1)
|
||||
assert(self._loggingDisabledCount >= 0)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function FSMMachine._Transition(self, eventResult)
|
||||
local result = eventResult
|
||||
while result[1] do
|
||||
-- perform the transition
|
||||
local currentStateObj = self._stateObjs[self._currentState]
|
||||
local toState = tremove(result, 1)
|
||||
local toStateObj = self._stateObjs[toState]
|
||||
if self._loggingDisabledCount == 0 then
|
||||
Log.RaiseStackLevel()
|
||||
Log.RaiseStackLevel()
|
||||
Log.Info("[%s] %s -> %s", self._name, self._currentState, toState)
|
||||
Log.LowerStackLevel()
|
||||
Log.LowerStackLevel()
|
||||
end
|
||||
assert(toStateObj and currentStateObj:_IsTransitionValid(toState), "invalid transition")
|
||||
self._inTransition = true
|
||||
currentStateObj:_Exit(self._context)
|
||||
self._currentState = toState
|
||||
result = TempTable.Acquire(toStateObj:_Enter(self._context, TempTable.UnpackAndRelease(result)))
|
||||
self._inTransition = false
|
||||
end
|
||||
TempTable.Release(result)
|
||||
end
|
||||
157
LibTSM/Util/FSMClasses/State.lua
Normal file
157
LibTSM/Util/FSMClasses/State.lua
Normal file
@@ -0,0 +1,157 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- FSMState Class.
|
||||
-- This class represents a single state within an @{FSMMachine}.
|
||||
-- @classmod FSMState
|
||||
|
||||
local _, TSM = ...
|
||||
local State = TSM.Init("Util.FSMClasses.State")
|
||||
local LibTSMClass = TSM.Include("LibTSMClass")
|
||||
local TempTable = TSM.Include("Util.TempTable")
|
||||
local FSMState = LibTSMClass.DefineClass("FSMState")
|
||||
local private = {
|
||||
eventTransitionHandlerCache = {},
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Class Meta Methods
|
||||
-- ============================================================================
|
||||
|
||||
function State.Create(name)
|
||||
return FSMState(name)
|
||||
end
|
||||
|
||||
function State.IsInstance(obj)
|
||||
return obj:__isa(FSMState)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Class Meta Methods
|
||||
-- ============================================================================
|
||||
|
||||
function FSMState.__init(self, name)
|
||||
self._name = name
|
||||
self._onEnterHandler = nil
|
||||
self._onExitHandler = nil
|
||||
self._transitionValid = {}
|
||||
self._events = {}
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Public Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
--- Set the OnEnter handler.
|
||||
-- This function is called upon entering the state.
|
||||
-- @tparam FSMState self The FSM state object
|
||||
-- @tparam ?function|string handler The handler function or a method name to call on the context object
|
||||
-- @treturn FSMState The FSM state object
|
||||
function FSMState.SetOnEnter(self, handler)
|
||||
assert(type(handler) == "function" or type(handler) == "string")
|
||||
self._onEnterHandler = handler
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the OnExit handler.
|
||||
-- This function is called upon existing the state.
|
||||
-- @tparam FSMState self The FSM state object
|
||||
-- @tparam ?function|string handler The handler function or a method name to call on the context object
|
||||
-- @treturn FSMState The FSM state object
|
||||
function FSMState.SetOnExit(self, handler)
|
||||
assert(type(handler) == "function" or type(handler) == "string")
|
||||
self._onExitHandler = handler
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a transition.
|
||||
-- @tparam FSMState self The FSM state object
|
||||
-- @tparam string toState The state this transition goes to
|
||||
-- @treturn FSMState The FSM state object
|
||||
function FSMState.AddTransition(self, toState)
|
||||
assert(not self._transitionValid[toState], "transition already exists")
|
||||
self._transitionValid[toState] = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a handled event.
|
||||
-- @tparam FSMState self The FSM state object
|
||||
-- @tparam string event The name of the event
|
||||
-- @tparam function handler The function called when the event occurs
|
||||
-- @treturn FSMState The FSM state object
|
||||
function FSMState.AddEvent(self, event, handler)
|
||||
assert(not self._events[event], "event already exists")
|
||||
self._events[event] = handler
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a simple event-based transition.
|
||||
-- @tparam FSMState self The FSM state object
|
||||
-- @tparam string event The event name
|
||||
-- @tparam string toState The state to transition to
|
||||
-- @treturn FSMState The FSM state object
|
||||
function FSMState.AddEventTransition(self, event, toState)
|
||||
if not private.eventTransitionHandlerCache[toState] then
|
||||
private.eventTransitionHandlerCache[toState] = function(context, ...)
|
||||
return toState, ...
|
||||
end
|
||||
end
|
||||
return self:AddEvent(event, private.eventTransitionHandlerCache[toState])
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Class Methods
|
||||
-- ============================================================================
|
||||
|
||||
function FSMState._GetName(self)
|
||||
return self._name
|
||||
end
|
||||
|
||||
function FSMState._ToStateIterator(self)
|
||||
local temp = TempTable.Acquire()
|
||||
for toState in pairs(self._transitionValid) do
|
||||
tinsert(temp, toState)
|
||||
end
|
||||
return TempTable.Iterator(temp)
|
||||
end
|
||||
|
||||
function FSMState._IsTransitionValid(self, toState)
|
||||
return self._transitionValid[toState]
|
||||
end
|
||||
|
||||
function FSMState._HasEventHandler(self, event)
|
||||
return self._events[event] and true or false
|
||||
end
|
||||
|
||||
function FSMState._ProcessEvent(self, event, context, ...)
|
||||
return self:_HandlerHelper(self._events[event], context, ...)
|
||||
end
|
||||
|
||||
function FSMState._Enter(self, context, ...)
|
||||
return self:_HandlerHelper(self._onEnterHandler, context, ...)
|
||||
end
|
||||
|
||||
function FSMState._Exit(self, context)
|
||||
return self:_HandlerHelper(self._onExitHandler, context)
|
||||
end
|
||||
|
||||
function FSMState._HandlerHelper(self, handler, context, ...)
|
||||
if type(handler) == "function" then
|
||||
return handler(context, ...)
|
||||
elseif type(handler) == "string" then
|
||||
return context[handler](context, ...)
|
||||
elseif handler ~= nil then
|
||||
error("Invalid handler: "..tostring(handler))
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user