Skip to main content
Skip to main content

StateMachine

StateMachine

Description

A base, abstract state machine.

Functions

callStateFunction

Description

Calls a function with the given name on every state.

Definition

callStateFunction(string functionName, any ...)

Arguments

stringfunctionNameThe name of the function to call.
any...The parameters to pass along.

Code

function StateMachine:callStateFunction(functionName, .. .)

-- Push the call forwards to all states with the function.
for _, state in pairs( self.states) do
if state[functionName] ~ = nil then
state[functionName](state, .. .)
end
end
end

changeState

Description

Changes the current state of the machine to the given state.

Definition

changeState(BaseStateMachineState newState, any ...)

Arguments

BaseStateMachineStatenewStateThe state to switch to.
any...The arguments passed into the onStateEntered function of the given state.

Code

function StateMachine:changeState(newState, .. .)
-- log("State change", self:getNameOfState(self.currentState), "to", self:getNameOfState(newState))

-- Keep track of the old state.
local previousState = self.currentState

-- Switch the state and alert the states of the change.
self.currentState = newState
previousState:onStateExited(newState)
newState:onStateEntered(previousState, .. .)
end

determineState

Description

Determines the best state to switch to based on the states' calculateIfValidEntryState function. Switches to the first state that returns true, defaulting to self.defaultState.

Definition

determineState()

Code

function StateMachine:determineState()

-- Go over each state, the first one to be valid is the new state.
for _, state in pairs( self.states) do
if state:calculateIfValidEntryState() then
self:changeState(state)
return
end
end

-- Since no state was chosen, use the default state.
self:changeState( self.defaultState)
end

getCurrentStateIndex

Description

Gets the index of the current state. Internally calls self:getIndexOfState(self.currentState).

Definition

getCurrentStateIndex()

Return Values

anyindexThe index of the current state.

Code

function StateMachine:getCurrentStateIndex()
return self:getIndexOfState( self.currentState)
end

getCurrentStateName

Description

Gets the name of the current state. Internally calls self:getNameOfState(self.currentState).

Definition

getCurrentStateName()

Return Values

anynameThe name of the current state.

Code

function StateMachine:getCurrentStateName()
return self:getNameOfState( self.currentState)
end

getIndexOfState

Description

Gets the index of the given state.

Definition

getIndexOfState(BaseStateMachineState state)

Arguments

BaseStateMachineStatestateThe state whose index should be returned.

Return Values

BaseStateMachineStateindexThe index of the state, or nil if the state is not in the machine.

Code

function StateMachine:getIndexOfState(state)

--#debug Assert.isType(state, "table")

-- If mappings have not yet been created, do so.
if not self:getHasMappings() then
self:createStateIndexNameMapping()
end

for i, otherState in ipairs( self.sortedStates) do
if otherState = = state then
return i
end
end

return nil
end

getIsPassive

Description

Gets the passive value for the state machine. Passive state machines do not change states during an update, and must be explicitly told when to change state.

Definition

getIsPassive()

Return Values

BaseStateMachineStateisPassiveTrue if this state machine is passive; otherwise false.

Code

function StateMachine:getIsPassive()
return self.isPassive
end

getNameOfState

Description

Gets the name of the given state.

Definition

getNameOfState(BaseStateMachineState state)

Arguments

BaseStateMachineStatestateThe state whose name should be returned.

Return Values

BaseStateMachineStatenameThe name of the state, or nil if the state is not in the machine.

Code

function StateMachine:getNameOfState(state)

if state = = nil then
return "No state"
end

-- If the state is missing a name, initialise the state names.
if string.isNilOrWhitespace(state.name) then
self:initialiseStateNames()
end

-- Return the state's name,
return state.name
end

getStateByIndex

Description

Gets the state associated with the given index.

Definition

getStateByIndex(integer index)

Arguments

integerindexThe index of the state to get.

Return Values

integerstateThe state with the associated index, or nil if none exists.

Code

function StateMachine:getStateByIndex(index)

--#debug Assert.isType(index, "number")

-- If mappings have not yet been created, do so.
if not self:getHasMappings() then
self:createStateIndexNameMapping()
end

local state = self.sortedStates[index]
return state
end

implementStateInterface

Description

Copies all members from the BaseStateMachineState class into the class table used to call this function. Call this on the class table just after the Class() call to allow a state machine to also act as a state.

Definition

implementStateInterface()

Code

function StateMachine:implementStateInterface()

-- Copy each value over from the base state class table over to this one.Don't overwrite anything.
for name, value in pairs( BaseStateMachineState ) do
if not self [name] then
self [name] = value
end
end
end

initialiseStateTransitions

Description

Goes over each state and calls the BaseStateMachineState.createTransitions function. This is not called by the base class by default.

Definition

initialiseStateTransitions(any ...)

Arguments

any...The arguments passed into the createTransitions function of the given state.

Code

function StateMachine:initialiseStateTransitions( .. .)
for _, state in pairs( self.states) do
state:createTransitions( .. .)
end
end

new

Description

Creates a base state machine. This should only be done within a derived class, as this is an abstract class.

Definition

new(table custom_mt)

Arguments

tablecustom_mtThe derived metatable to use.

Return Values

tableinstanceThe created instance.

Code

function StateMachine.new(custom_mt)

-- Create the instance.
local self = setmetatable( { } , custom_mt or StateMachine _mt)

-- Start with no state and no default state.
self.currentState = nil
self.defaultState = nil

-- The passive value.If a state machine is passive, it never transitions between states during updates, it must be explicitly driven.
self.isPassive = false

-- The states table.
self.states = { }

-- The array of states by index, sorted by state name alphabetically.
self.sortedStates = nil

-- Return the created instance.
return self
end

resolveCurrentState

Description

Resolves the current state of this state machine. If this.currentState has a resolveCurrentState function, its result is returned; otherwise the state itself is returned.

Definition

resolveCurrentState()

Return Values

tablecurrentStateThe fully resolved current state.

Code

function StateMachine:resolveCurrentState()

-- If the current state has a function to resolve the current state(e.g.if it is a state machine), return the result of the function call; otherwise return the current state.
return self.currentState.resolveCurrentState ~ = nil and self.currentState:resolveCurrentState() or self.currentState
end

setIsPassive

Description

Sets the passive value for the state machine.

Definition

setIsPassive(boolean isPassive)

Arguments

booleanisPassiveIf this state machine should be passive.

Code

function StateMachine:setIsPassive(isPassive)
self.isPassive = isPassive = = true
end

update

Description

The update function which updates each state.

Definition

update(float dt)

Arguments

floatdtDelta time in ms.

Code

function StateMachine:update(dt)
--#profile RemoteProfiler.zoneBeginN("StateMachine-update")
-- Check to see if any state wants to be forced, if the state machine is not passive.
if not self:getIsPassive() then
for _, state in pairs( self.states) do

-- If the state should be forced, switch to it and skip the other states.
if state:calculateIfShouldBeForced() then
if state ~ = self.currentState then
--#profile RemoteProfiler.zoneBeginN("StateMachine-stateChange: " .. (state.name or state.__CLASSNAME or ""))
self:changeState(state)
--#profile RemoteProfiler.zoneEnd()
break
end
end
end
end

-- Update the states based on if they're current or not.
for _, state in pairs( self.states) do
--#profile RemoteProfiler.zoneBeginN("StateMachine-stateUpdate: " .. (state.name or state.__CLASSNAME or ""))
if state ~ = self.currentState then
state:updateAsInactive(dt)
else
state:updateAsCurrent(dt)
end
--#profile RemoteProfiler.zoneEnd()
end
--#profile RemoteProfiler.zoneEnd()
end