open_toontown_panda3d/direct/src/fsm/FSM.py

360 lines
13 KiB
Python

"""Finite State Machine module: contains the FSM class"""
from DirectObject import *
import types
class FSM(DirectObject):
"""FSM class: Finite State Machine class"""
# create FSM DirectNotify category
notify = directNotify.newCategory("FSM")
# special methods
# these are flags that tell the FSM what to do when an
# undefined transition is requested:
ALLOW = 0 # print a warning, and do the transition
DISALLOW = 1 # silently ignore the request (don't do the transition)
DISALLOW_VERBOSE = 2 # print a warning, and don't do the transition
ERROR = 3 # print an error message and raise an exception
def __init__(self, name, states=[], initialStateName=None,
finalStateName=None, onUndefTransition=DISALLOW_VERBOSE):
"""__init__(self, string, State[], string, string, int)
FSM constructor: takes name, list of states, initial state and
final state as:
fsm = FSM.FSM('stopLight',
[ State.State('red', enterRed, exitRed, ['green']),
State.State('yellow', enterYellow, exitYellow, ['red']),
State.State('green', enterGreen, exitGreen, ['yellow']) ],
'red',
'red')
each state's last argument, a list of allowed state transitions,
is optional; if left out (or explicitly specified to be
State.State.Any) then any transition from the state is 'defined'
and allowed
'onUndefTransition' flag determines behavior when undefined
transition is requested; see flag definitions above
"""
self.setName(name)
self.setStates(states)
self.setInitialState(initialStateName)
self.setFinalState(finalStateName)
self.onUndefTransition = onUndefTransition
# Flag to see if we are inspecting
self.inspecting = 0
# We do not enter the initial state to separate
# construction from activation
self.__currentState = None
# We set this while we are modifying the state. No one else
# should recursively attempt to modify the state while we are
# doing this.
self.__internalStateInFlux = 0
# I know this isn't how __repr__ is supposed to be used, but it
# is nice and convenient.
def __repr__(self):
return self.__str__()
def __str__(self):
"""__str__(self)
Print out something useful about the fsm
"""
currentState = self.getCurrentState()
if currentState:
str = ("FSM " + self.getName() + ' in state "' +
currentState.getName() + '"')
else:
str = ("FSM " + self.getName() + ' not in any state')
return str
def enterInitialState(self, argList=[]):
assert(not self.__internalStateInFlux)
if self.__currentState == self.__initialState:
return
assert(self.__currentState == None)
self.__internalStateInFlux = 1
self.__enter(self.__initialState, argList)
assert(not self.__internalStateInFlux)
# Jesse decided that simpler was better with the __str__ function
def __str_not__(self):
"""__str__(self)"""
return "FSM: name = %s \n states = %s \n initial = %s \n final = %s \n current = %s" \
% (self.__name, self.__states, self.__initialState,
self.__finalState, self.__currentState)
# setters and getters
def getName(self):
"""getName(self)"""
return(self.__name)
def setName(self, name):
"""setName(self, string)"""
self.__name = name
def getStates(self):
"""getStates(self)"""
return(self.__states)
def setStates(self, states):
"""setStates(self, State[])"""
self.__states = states
def addState(self, state):
"""addState(state)"""
self.__states.append(state)
def getInitialState(self):
"""getInitialState(self)"""
return(self.__initialState)
def setInitialState(self, initialStateName):
"""setInitialState(self, string)"""
self.__initialState = self.getStateNamed(initialStateName)
def getFinalState(self):
"""getFinalState(self)"""
return(self.__finalState)
def setFinalState(self, finalStateName):
"""setFinalState(self, string)"""
self.__finalState = self.getStateNamed(finalStateName)
def requestFinalState(self):
self.request(self.__finalState.getName())
def getCurrentState(self):
"""getCurrentState(self)"""
return(self.__currentState)
# lookup funcs
def getStateNamed(self, stateName):
"""getStateNamed(self, string)
Return the state with given name if found, issue warning otherwise"""
for state in self.__states:
if (state.getName() == stateName):
return state
FSM.notify.warning("[%s] : getStateNamed: %s, no such state" %
(self.__name, str(stateName)))
# basic FSM functionality
def __exitCurrent(self, argList):
"""__exitCurrent(self)
Exit the current state"""
assert(self.__internalStateInFlux)
if FSM.notify.getDebug():
FSM.notify.debug("[%s]: exiting %s" % (self.__name,
self.__currentState.getName()))
self.__currentState.exit(argList)
# Only send the state change event if we are inspecting it
# If this event turns out to be generally useful, we can
# turn it on all the time, but for now nobody else is using it
if self.inspecting:
messenger.send(self.getName() + '_' +
self.__currentState.getName() + '_exited')
self.__currentState = None
def __enter(self, aState, argList=[]):
"""__enter(self, State)
Enter a given state, if it exists"""
assert(self.__internalStateInFlux)
if (aState in self.__states):
if FSM.notify.getDebug():
FSM.notify.debug("[%s]: entering %s" % (self.__name,
aState.getName()))
self.__currentState = aState
# Only send the state change event if we are inspecting it
# If this event turns out to be generally useful, we can
# turn it on all the time, but for now nobody else is using it
if self.inspecting:
messenger.send(self.getName() + '_' +
aState.getName() + '_entered')
# Once we begin entering the new state, we're allow to
# recursively request a transition to another state.
# Indicate this by marking our internal state no longer in
# flux.
self.__internalStateInFlux = 0
aState.enter(argList)
else:
# notify.error is going to raise an exception; reset the
# flux flag first
self.__internalStateInFlux = 0
FSM.notify.error("[%s]: enter: no such state" % (self.__name))
def __transition(self, aState, enterArgList=[], exitArgList=[]):
"""__transition(self, State, enterArgList, exitArgList)
Exit currentState and enter given one"""
assert(not self.__internalStateInFlux)
self.__internalStateInFlux = 1
self.__exitCurrent(exitArgList)
self.__enter(aState, enterArgList)
assert(not self.__internalStateInFlux)
def request(self, aStateName, enterArgList=[], exitArgList=[],
force=0):
"""request(self, string)
Attempt transition from currentState to given one.
Return true is transition exists to given state,
false otherwise.
"""
# If you trigger this assertion failure, you must have
# recursively requested a state transition from within the
# exitState() function for the previous state. This is not
# supported because we're not fully transitioned into the new
# state yet.
assert(not self.__internalStateInFlux)
if not self.__currentState:
# Make this a warning for now
FSM.notify.warning("[%s]: request: never entered initial state" %
(self.__name))
self.__currentState = self.__initialState
if isinstance(aStateName, types.StringType):
aState = self.getStateNamed(aStateName)
else:
# Allow the caller to pass in a state in itself, not just
# the name of a state.
aState = aStateName
aStateName = aState.getName()
if aState == None:
FSM.notify.error("[%s]: request: %s, no such state" %
(self.__name, aStateName))
# is the transition defined? if it isn't, should we allow it?
transitionDefined = self.__currentState.isTransitionDefined(aStateName)
transitionAllowed = transitionDefined
if self.onUndefTransition == FSM.ALLOW:
transitionAllowed = 1
if not transitionDefined:
# the transition is not defined, but we're going to do it
# anyway. print a warning.
FSM.notify.warning(
"[%s]: performing undefined transition from %s to %s" %
(self.__name,
self.__currentState.getName(),
aStateName))
if transitionAllowed or force:
self.__transition(aState,
enterArgList,
exitArgList)
return 1
# We can implicitly always transition to our final state.
elif (aStateName == self.__finalState.getName()):
if (self.__currentState == self.__finalState):
# Do not do the transition if we are already in the final state
if FSM.notify.getDebug():
FSM.notify.debug("[%s]: already in final state: %s" %
(self.__name, aStateName))
return 1
else:
# Force a transition to allow for cleanup
if FSM.notify.getDebug():
FSM.notify.debug("[%s]: implicit transition to final state: %s" %
(self.__name, aStateName))
self.__transition(aState,
enterArgList,
exitArgList)
return 1
# are we already in this state?
elif (aStateName == self.__currentState.getName()):
if FSM.notify.getDebug():
FSM.notify.debug("[%s]: already in state %s and no self transition" %
(self.__name, aStateName))
return 0
else:
msg = ("[%s]: no transition exists from %s to %s" %
(self.__name,
self.__currentState.getName(),
aStateName))
if self.onUndefTransition == FSM.ERROR:
FSM.notify.error(msg)
elif self.onUndefTransition == FSM.DISALLOW_VERBOSE:
FSM.notify.warning(msg)
return 0
def forceTransition(self, aStateName, enterArgList=[], exitArgList=[]):
""" force a transition -- for debugging ONLY """
self.request(aStateName, enterArgList, exitArgList, force=1)
def conditional_request(self, aStateName, enterArgList=[], exitArgList=[]):
"""request(self, string)
'if this transition is defined, do it'
Attempt transition from currentState to given one, if it exists.
Return true if transition exists to given state,
false otherwise. It is NOT an error/warning to attempt a cond_request
if the transition doesn't exist. This lets people be sloppy about
FSM transitions, letting the same fn be used for different states
that may not have the same out transitions.
"""
assert(not self.__internalStateInFlux)
if not self.__currentState:
# Make this a warning for now
FSM.notify.warning("[%s]: request: never entered initial state" %
(self.__name))
self.__currentState = self.__initialState
if isinstance(aStateName, types.StringType):
aState = self.getStateNamed(aStateName)
else:
# Allow the caller to pass in a state in itself, not just
# the name of a state.
aState = aStateName
aStateName = aState.getName()
if aState == None:
FSM.notify.error("[%s]: request: %s, no such state" %
(self.__name, aStateName))
transitionDefined = (
self.__currentState.isTransitionDefined(aStateName) or
aStateName in [self.__currentState.getName(),
self.__finalState.getName()]
)
if transitionDefined:
return self.request(aStateName, enterArgList, exitArgList)
else:
FSM.notify.debug("[%s]: condition_request: %s, transition doesnt exist" %
(self.__name, aStateName))
return 0
def view(self):
import FSMInspector
FSMInspector.FSMInspector(self)