mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 02:42:49 -04:00
rename FSM -> ClassicFSM, introduce new FSM code
This commit is contained in:
parent
193984d955
commit
13437d3ff5
368
direct/src/fsm/ClassicFSM.py
Normal file
368
direct/src/fsm/ClassicFSM.py
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
"""Finite State Machine module: contains the ClassicFSM class.
|
||||||
|
|
||||||
|
This module and class exist only for backward compatibility with
|
||||||
|
existing code. New code should use the FSM module instead.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from DirectObject import *
|
||||||
|
import types
|
||||||
|
|
||||||
|
class ClassicFSM(DirectObject):
|
||||||
|
"""ClassicFSM class: Finite State Machine class.
|
||||||
|
|
||||||
|
This module and class exist only for backward compatibility with
|
||||||
|
existing code. New code should use the FSM class instead.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# create ClassicFSM DirectNotify category
|
||||||
|
notify = directNotify.newCategory("ClassicFSM")
|
||||||
|
|
||||||
|
# special methods
|
||||||
|
|
||||||
|
# these are flags that tell the ClassicFSM 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)
|
||||||
|
|
||||||
|
ClassicFSM constructor: takes name, list of states, initial state and
|
||||||
|
final state as:
|
||||||
|
|
||||||
|
fsm = ClassicFSM.ClassicFSM('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 = ("ClassicFSM " + self.getName() + ' in state "' +
|
||||||
|
currentState.getName() + '"')
|
||||||
|
else:
|
||||||
|
str = ("ClassicFSM " + 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 "ClassicFSM: 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
|
||||||
|
ClassicFSM.notify.warning("[%s] : getStateNamed: %s, no such state" %
|
||||||
|
(self.__name, str(stateName)))
|
||||||
|
|
||||||
|
|
||||||
|
# basic ClassicFSM functionality
|
||||||
|
|
||||||
|
def __exitCurrent(self, argList):
|
||||||
|
"""__exitCurrent(self)
|
||||||
|
Exit the current state"""
|
||||||
|
assert(self.__internalStateInFlux)
|
||||||
|
if ClassicFSM.notify.getDebug():
|
||||||
|
ClassicFSM.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 ClassicFSM.notify.getDebug():
|
||||||
|
ClassicFSM.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
|
||||||
|
ClassicFSM.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
|
||||||
|
ClassicFSM.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:
|
||||||
|
ClassicFSM.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 == ClassicFSM.ALLOW:
|
||||||
|
transitionAllowed = 1
|
||||||
|
if not transitionDefined:
|
||||||
|
# the transition is not defined, but we're going to do it
|
||||||
|
# anyway. print a warning.
|
||||||
|
ClassicFSM.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 ClassicFSM.notify.getDebug():
|
||||||
|
ClassicFSM.notify.debug("[%s]: already in final state: %s" %
|
||||||
|
(self.__name, aStateName))
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
# Force a transition to allow for cleanup
|
||||||
|
if ClassicFSM.notify.getDebug():
|
||||||
|
ClassicFSM.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 ClassicFSM.notify.getDebug():
|
||||||
|
ClassicFSM.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 == ClassicFSM.ERROR:
|
||||||
|
ClassicFSM.notify.error(msg)
|
||||||
|
elif self.onUndefTransition == ClassicFSM.DISALLOW_VERBOSE:
|
||||||
|
ClassicFSM.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
|
||||||
|
ClassicFSM 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
|
||||||
|
ClassicFSM.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:
|
||||||
|
ClassicFSM.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:
|
||||||
|
ClassicFSM.notify.debug("[%s]: condition_request: %s, transition doesnt exist" %
|
||||||
|
(self.__name, aStateName))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def view(self):
|
||||||
|
import FSMInspector
|
||||||
|
FSMInspector.FSMInspector(self)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,68 +1,248 @@
|
|||||||
"""Finite State Machine module: contains the FSM class"""
|
"""The new Finite State Machine module. This replaces the modules
|
||||||
|
previously called FSM.py (now called ClassicFSM.py).
|
||||||
|
"""
|
||||||
|
|
||||||
from DirectObject import *
|
import DirectObject
|
||||||
|
import DirectNotifyGlobal
|
||||||
import types
|
import types
|
||||||
|
import string
|
||||||
|
|
||||||
class FSM(DirectObject):
|
class FSM(DirectObject.DirectObject):
|
||||||
"""FSM class: Finite State Machine class"""
|
"""A Finite State Machine. This is intended to be the base class
|
||||||
|
of any number of specific machines, which consist of a collection
|
||||||
|
of states and transitions, and rules to switch between states
|
||||||
|
according to arbitrary input data.
|
||||||
|
|
||||||
# create FSM DirectNotify category
|
The states of an FSM are defined implicitly. Each state is
|
||||||
notify = directNotify.newCategory("FSM")
|
identified by a string, which by convention begins with a capital
|
||||||
|
letter. (Also by convention, strings passed to request that are
|
||||||
|
not state names should begin with a lowercase letter.)
|
||||||
|
|
||||||
# special methods
|
To define specialized behavior when entering or exiting a
|
||||||
|
particular state, define a method named enterState() and/or
|
||||||
|
exitState(), where "State" is the name of the state, e.g.:
|
||||||
|
|
||||||
# these are flags that tell the FSM what to do when an
|
def enterRed(self, oldState, newState):
|
||||||
# undefined transition is requested:
|
... do stuff ...
|
||||||
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,
|
def exitRed(self, oldState, newState):
|
||||||
finalStateName=None, onUndefTransition=DISALLOW_VERBOSE):
|
... cleanup stuff ...
|
||||||
"""__init__(self, string, State[], string, string, int)
|
|
||||||
|
|
||||||
FSM constructor: takes name, list of states, initial state and
|
def enterYellow(self, oldState, newState):
|
||||||
final state as:
|
... do stuff ...
|
||||||
|
|
||||||
fsm = FSM.FSM('stopLight',
|
def exitYellow(self, oldState, newState):
|
||||||
[ State.State('red', enterRed, exitRed, ['green']),
|
... cleanup stuff ...
|
||||||
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,
|
def enterGreen(self, oldState, newState):
|
||||||
is optional; if left out (or explicitly specified to be
|
... do stuff ...
|
||||||
State.State.Any) then any transition from the state is 'defined'
|
|
||||||
and allowed
|
|
||||||
|
|
||||||
'onUndefTransition' flag determines behavior when undefined
|
def exitGreen(self, oldState, newState):
|
||||||
transition is requested; see flag definitions above
|
... cleanup stuff ...
|
||||||
|
|
||||||
"""
|
Both functions are supplied the previous state name and the new
|
||||||
|
state name we are transitioning to. (Of course, the newState
|
||||||
|
passed to enterRed, and the oldState passed to exitRed, will
|
||||||
|
always be "Red".)
|
||||||
|
|
||||||
self.setName(name)
|
Both functions are optional. If either function is omitted, the
|
||||||
self.setStates(states)
|
state is still defined, but nothing is done when transitioning
|
||||||
self.setInitialState(initialStateName)
|
into (or out of) the state.
|
||||||
self.setFinalState(finalStateName)
|
|
||||||
|
|
||||||
self.onUndefTransition = onUndefTransition
|
Additionally, you may define a filterState() function for each
|
||||||
|
state. The purpose of this function is to decide what state to
|
||||||
|
transition to next, if any, on receipt of a particular input. The
|
||||||
|
input is always a string and a tuple of optional parameters (which
|
||||||
|
is often empty), and the return value should either be None to do
|
||||||
|
nothing, or the name of the state to transition into. For
|
||||||
|
example:
|
||||||
|
|
||||||
# Flag to see if we are inspecting
|
def filterRed(self, request, args):
|
||||||
self.inspecting = 0
|
if request in ['Green']:
|
||||||
|
return request
|
||||||
|
return None
|
||||||
|
|
||||||
# We do not enter the initial state to separate
|
def filterYellow(self, request, args):
|
||||||
# construction from activation
|
if request in ['Red']:
|
||||||
self.__currentState = None
|
return request
|
||||||
|
return None
|
||||||
|
|
||||||
# We set this while we are modifying the state. No one else
|
def filterGreen(self, request, args):
|
||||||
# should recursively attempt to modify the state while we are
|
if request in ['Yellow']:
|
||||||
# doing this.
|
return request
|
||||||
self.__internalStateInFlux = 0
|
return None
|
||||||
|
|
||||||
# I know this isn't how __repr__ is supposed to be used, but it
|
As above, the filterState() functions are optional. If any is
|
||||||
# is nice and convenient.
|
omitted, the defaultFilter() method is called instead. The
|
||||||
|
default definition of defaultFilter() always returns None, thus
|
||||||
|
disallowing all unexpected transitions. This default behavior may
|
||||||
|
be overridden in a derived class.
|
||||||
|
|
||||||
|
FSM.state may be queried at any time other than during the
|
||||||
|
handling of the enter() and exit() functions. During these
|
||||||
|
functions, FSM.state contains the value None (you are not really
|
||||||
|
in any state during the transition). At other times, FSM.state
|
||||||
|
contains the name of the current state.
|
||||||
|
|
||||||
|
Initially, the FSM is in state 'Off'. It does not call enterOff()
|
||||||
|
at construction time; it is simply in Off already by convention.
|
||||||
|
If you need to call code in enterOff() to initialize your FSM
|
||||||
|
properly, call it explicitly in the constructor. Similarly, when
|
||||||
|
cleanup() is called or the FSM is destructed, the FSM transitions
|
||||||
|
back to 'Off' by convention. (It does call enterOff() at this
|
||||||
|
point, but does not call exitOff().)
|
||||||
|
|
||||||
|
To implement nested hierarchical FSM's, simply create a nested FSM
|
||||||
|
and store it on the class within the appropriate enterState()
|
||||||
|
function, and clean it up within the corresponding exitState()
|
||||||
|
function.
|
||||||
|
|
||||||
|
See the code in SampleFSM.py for further examples.
|
||||||
|
"""
|
||||||
|
|
||||||
|
notify = DirectNotifyGlobal.directNotify.newCategory("FSM")
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
# Initially, we are in the Off state by convention.
|
||||||
|
self.state = 'Off'
|
||||||
|
|
||||||
|
# This member lists the default transitions that are accepted
|
||||||
|
# without question by the defaultFilter. It's a map of state
|
||||||
|
# names to a list of legal target state names from that state.
|
||||||
|
# Define it only if you want to use the classic FSM model of
|
||||||
|
# defining all (or most) of your transitions up front. If
|
||||||
|
# this is set to None (the default), all named-state
|
||||||
|
# transitions (that is, those requests whose name begins with
|
||||||
|
# a capital letter) are allowed. If it is set to an empty
|
||||||
|
# map, no transitions are implicitly allowed--all transitions
|
||||||
|
# must be approved by some filter function.
|
||||||
|
self.defaultTransitions = None
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
# A convenience function to force the FSM to clean itself up
|
||||||
|
# by transitioning to the "Off" state.
|
||||||
|
assert(self.state)
|
||||||
|
if self.state != 'Off':
|
||||||
|
self.__setState('Off')
|
||||||
|
|
||||||
|
def forceTransition(self, newState):
|
||||||
|
"""Changes unconditionally to the indicated state. This
|
||||||
|
bypasses the filterState() function, and just calls
|
||||||
|
exitState() followed by enterState()."""
|
||||||
|
|
||||||
|
assert(isinstance(newState, types.StringType))
|
||||||
|
|
||||||
|
self.__setState(newState)
|
||||||
|
|
||||||
|
def request(self, request, *args):
|
||||||
|
"""Requests a state transition (or other behavior). The request
|
||||||
|
parameter should be a string. The request, along with any
|
||||||
|
additional arguments, is passed to the current filterState()
|
||||||
|
function. If filterState() returns a string, the FSM
|
||||||
|
transitions to that state.
|
||||||
|
|
||||||
|
The return value is the same as the return value of
|
||||||
|
filterState() (that is, None if the request does not provoke a
|
||||||
|
state transition, or the name of the new state otherwise.)
|
||||||
|
|
||||||
|
If the FSM is currently in transition (i.e. in the middle of
|
||||||
|
executing an enterState or exitState function), the request is
|
||||||
|
denied and None is returned."""
|
||||||
|
|
||||||
|
assert(isinstance(request, types.StringType))
|
||||||
|
self.notify.debug("%s.request(%s, %s" % (self.name, request, str(args)[1:]))
|
||||||
|
|
||||||
|
if not self.state:
|
||||||
|
self.notify.warning("rejecting request %s while FSM is in transition." % (request))
|
||||||
|
return None
|
||||||
|
|
||||||
|
func = getattr(self, "filter" + self.state, None)
|
||||||
|
if not func:
|
||||||
|
# If there's no matching filterState() function, call
|
||||||
|
# defaultFilter() instead.
|
||||||
|
func = self.defaultFilter
|
||||||
|
result = func(request, args)
|
||||||
|
if result:
|
||||||
|
assert(isinstance(result, types.StringType))
|
||||||
|
self.__setState(result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def defaultFilter(self, request, args):
|
||||||
|
"""This is the function that is called if there is no
|
||||||
|
filterState() method for a particular state name. By default,
|
||||||
|
the filter defined here in the base class always returns
|
||||||
|
None, disallowing any transition. Specialized FSM's may wish
|
||||||
|
to redefine this default filter (for instance, to always
|
||||||
|
return the request itself, thus allowing any transition.)."""
|
||||||
|
|
||||||
|
if request == 'Off':
|
||||||
|
# We can always go to the "Off" state.
|
||||||
|
return request
|
||||||
|
|
||||||
|
if self.defaultTransitions is None:
|
||||||
|
# If self.defaultTransitions is None, it means to accept
|
||||||
|
# all requests whose name begins with a capital letter.
|
||||||
|
# These are direct requests to a particular state.
|
||||||
|
if request[0] in string.uppercase:
|
||||||
|
return request
|
||||||
|
|
||||||
|
else:
|
||||||
|
# If self.defaultTransitions is not None, it is a map of
|
||||||
|
# allowed transitions from each state. That is, each key
|
||||||
|
# of the map is the current state name; for that key, the
|
||||||
|
# value is a list of allowed transitions from the
|
||||||
|
# indicated state.
|
||||||
|
if request in self.defaultTransitions.get(self.state, []):
|
||||||
|
# This transition is listed in the defaultTransitions map;
|
||||||
|
# accept it.
|
||||||
|
return request
|
||||||
|
|
||||||
|
# If self.defaultTransitions is not None, it is an error
|
||||||
|
# to request a direct state transition (capital letter
|
||||||
|
# request) not listed in defaultTransitions and not
|
||||||
|
# handled by an earlier filter.
|
||||||
|
if request[0] in string.uppercase:
|
||||||
|
self.notify.error("%s rejecting request %s from state %s." % (self.name, request, self.state))
|
||||||
|
|
||||||
|
# In either case, we quietly ignore unhandled command
|
||||||
|
# (lowercase) requests.
|
||||||
|
assert(self.notify.debug("%s ignoring request %s from state %s." % (self.name, request, self.state)))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def filterOff(self, request, args):
|
||||||
|
"""From the off state, we can always go directly to any other
|
||||||
|
state."""
|
||||||
|
if request[0] in string.uppercase:
|
||||||
|
return request
|
||||||
|
return self.defaultFilter(request, args)
|
||||||
|
|
||||||
|
|
||||||
|
def __setState(self, newState):
|
||||||
|
# Internal function to change unconditionally to the indicated
|
||||||
|
# state.
|
||||||
|
assert(self.state)
|
||||||
|
|
||||||
|
oldState = self.state
|
||||||
|
self.state = None
|
||||||
|
self.__callTransitionFunc("exit" + oldState, oldState, newState)
|
||||||
|
self.__callTransitionFunc("enter" + newState, oldState, newState)
|
||||||
|
self.state = newState
|
||||||
|
|
||||||
|
|
||||||
|
def __callTransitionFunc(self, name, oldState, newState):
|
||||||
|
# Calls the appropriate enter or exit function when
|
||||||
|
# transitioning between states, if it exists.
|
||||||
|
assert(self.state == None)
|
||||||
|
|
||||||
|
func = getattr(self, name, None)
|
||||||
|
if func:
|
||||||
|
func(oldState, newState)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
|
||||||
@ -70,290 +250,8 @@ class FSM(DirectObject):
|
|||||||
"""__str__(self)
|
"""__str__(self)
|
||||||
Print out something useful about the fsm
|
Print out something useful about the fsm
|
||||||
"""
|
"""
|
||||||
currentState = self.getCurrentState()
|
if self.state:
|
||||||
if currentState:
|
str = ("FSM " + self.name + ' in state "' + self.state + '"')
|
||||||
str = ("FSM " + self.getName() + ' in state "' +
|
|
||||||
currentState.getName() + '"')
|
|
||||||
else:
|
else:
|
||||||
str = ("FSM " + self.getName() + ' not in any state')
|
str = ("FSM " + self.name + ' not in any state')
|
||||||
return str
|
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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
import DirectNotifyGlobal
|
import DirectNotifyGlobal
|
||||||
#import DistributedObject
|
#import DistributedObject
|
||||||
import FSM
|
import ClassicFSM
|
||||||
import State
|
import State
|
||||||
import Task
|
import Task
|
||||||
|
|
||||||
|
|
||||||
class FourState:
|
class FourState:
|
||||||
"""
|
"""
|
||||||
Generic four state FSM base class.
|
Generic four state ClassicFSM base class.
|
||||||
|
|
||||||
This is a mix-in class that expects that your derived class
|
This is a mix-in class that expects that your derived class
|
||||||
is a DistributedObject.
|
is a DistributedObject.
|
||||||
@ -34,7 +34,7 @@ class FourState:
|
|||||||
+------+
|
+------+
|
||||||
|
|
||||||
There is a fifth off state, but that is an implementation
|
There is a fifth off state, but that is an implementation
|
||||||
detail (and that's why it's not called a five state FSM).
|
detail (and that's why it's not called a five state ClassicFSM).
|
||||||
|
|
||||||
I found that this pattern repeated in several things I was
|
I found that this pattern repeated in several things I was
|
||||||
working on, so this base class was created.
|
working on, so this base class was created.
|
||||||
@ -120,7 +120,7 @@ class FourState:
|
|||||||
[names[1]]),
|
[names[1]]),
|
||||||
}
|
}
|
||||||
self.stateIndex = 0
|
self.stateIndex = 0
|
||||||
self.fsm = FSM.FSM('FourState',
|
self.fsm = ClassicFSM.ClassicFSM('FourState',
|
||||||
self.states.values(),
|
self.states.values(),
|
||||||
# Initial State
|
# Initial State
|
||||||
names[0],
|
names[0],
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
import DirectNotifyGlobal
|
import DirectNotifyGlobal
|
||||||
#import DistributedObjectAI
|
#import DistributedObjectAI
|
||||||
import FSM
|
import ClassicFSM
|
||||||
import State
|
import State
|
||||||
import Task
|
import Task
|
||||||
|
|
||||||
|
|
||||||
class FourStateAI:
|
class FourStateAI:
|
||||||
"""
|
"""
|
||||||
Generic four state FSM base class.
|
Generic four state ClassicFSM base class.
|
||||||
|
|
||||||
This is a mix-in class that expects that your derived class
|
This is a mix-in class that expects that your derived class
|
||||||
is a DistributedObjectAI.
|
is a DistributedObjectAI.
|
||||||
@ -34,7 +34,7 @@ class FourStateAI:
|
|||||||
+------+
|
+------+
|
||||||
|
|
||||||
There is a fifth off state, but that is an implementation
|
There is a fifth off state, but that is an implementation
|
||||||
detail (and that's why it's not called a five state FSM).
|
detail (and that's why it's not called a five state ClassicFSM).
|
||||||
|
|
||||||
I found that this pattern repeated in several things I was
|
I found that this pattern repeated in several things I was
|
||||||
working on, so this base class was created.
|
working on, so this base class was created.
|
||||||
@ -58,7 +58,7 @@ class FourStateAI:
|
|||||||
durations is a list of durations in seconds or None values.
|
durations is a list of durations in seconds or None values.
|
||||||
The list of duration values should be the same length
|
The list of duration values should be the same length
|
||||||
as the list of state names and the lists correspond.
|
as the list of state names and the lists correspond.
|
||||||
For each state, after n seconds, the FSM will move to
|
For each state, after n seconds, the ClassicFSM will move to
|
||||||
the next state. That does not happen for any duration
|
the next state. That does not happen for any duration
|
||||||
values of None.
|
values of None.
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ class FourStateAI:
|
|||||||
self.exitState4,
|
self.exitState4,
|
||||||
[names[1]]),
|
[names[1]]),
|
||||||
}
|
}
|
||||||
self.fsm = FSM.FSM('FourState',
|
self.fsm = ClassicFSM.ClassicFSM('FourState',
|
||||||
self.states.values(),
|
self.states.values(),
|
||||||
# Initial State
|
# Initial State
|
||||||
names[0],
|
names[0],
|
||||||
|
193
direct/src/fsm/SampleFSM.py
Normal file
193
direct/src/fsm/SampleFSM.py
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import FSM
|
||||||
|
from PandaModules import *
|
||||||
|
import Task
|
||||||
|
import string
|
||||||
|
|
||||||
|
|
||||||
|
class ClassicStyle(FSM.FSM):
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
FSM.FSM.__init__(self, name)
|
||||||
|
|
||||||
|
self.defaultTransitions = {
|
||||||
|
'Red' : ['Green'],
|
||||||
|
'Yellow' : ['Red'],
|
||||||
|
'Green' : ['Yellow'],
|
||||||
|
}
|
||||||
|
|
||||||
|
def enterRed(self, oldState, newState):
|
||||||
|
print "enterRed(self, '%s', '%s')" % (oldState, newState)
|
||||||
|
|
||||||
|
def exitRed(self, oldState, newState):
|
||||||
|
print "exitRed(self, '%s', '%s')" % (oldState, newState)
|
||||||
|
|
||||||
|
def enterYellow(self, oldState, newState):
|
||||||
|
print "enterYellow(self, '%s', '%s')" % (oldState, newState)
|
||||||
|
|
||||||
|
def exitYellow(self, oldState, newState):
|
||||||
|
print "exitYellow(self, '%s', '%s')" % (oldState, newState)
|
||||||
|
|
||||||
|
def enterGreen(self, oldState, newState):
|
||||||
|
print "enterGreen(self, '%s', '%s')" % (oldState, newState)
|
||||||
|
|
||||||
|
def exitGreen(self, oldState, newState):
|
||||||
|
print "exitGreen(self, '%s', '%s')" % (oldState, newState)
|
||||||
|
|
||||||
|
|
||||||
|
class NewStyle(FSM.FSM):
|
||||||
|
|
||||||
|
def enterRed(self, oldState, newState):
|
||||||
|
print "enterRed(self, '%s', '%s')" % (oldState, newState)
|
||||||
|
|
||||||
|
def filterRed(self, request, args):
|
||||||
|
print "filterRed(self, '%s', %s)" % (request, args)
|
||||||
|
if request == 'advance':
|
||||||
|
return 'Green'
|
||||||
|
return self.defaultFilter(request, args)
|
||||||
|
|
||||||
|
def exitRed(self, oldState, newState):
|
||||||
|
print "exitRed(self, '%s', '%s')" % (oldState, newState)
|
||||||
|
|
||||||
|
def enterYellow(self, oldState, newState):
|
||||||
|
print "enterYellow(self, '%s', '%s')" % (oldState, newState)
|
||||||
|
|
||||||
|
def filterYellow(self, request, args):
|
||||||
|
print "filterYellow(self, '%s', %s)" % (request, args)
|
||||||
|
if request == 'advance':
|
||||||
|
return 'Red'
|
||||||
|
return self.defaultFilter(request, args)
|
||||||
|
|
||||||
|
def exitYellow(self, oldState, newState):
|
||||||
|
print "exitYellow(self, '%s', '%s')" % (oldState, newState)
|
||||||
|
|
||||||
|
def enterGreen(self, oldState, newState):
|
||||||
|
print "enterGreen(self, '%s', '%s')" % (oldState, newState)
|
||||||
|
|
||||||
|
def filterGreen(self, request, args):
|
||||||
|
print "filterGreen(self, '%s', %s)" % (request, args)
|
||||||
|
if request == 'advance':
|
||||||
|
return 'Yellow'
|
||||||
|
return self.defaultFilter(request, args)
|
||||||
|
|
||||||
|
def exitGreen(self, oldState, newState):
|
||||||
|
print "exitGreen(self, '%s', '%s')" % (oldState, newState)
|
||||||
|
|
||||||
|
|
||||||
|
class ToonEyes(FSM.FSM):
|
||||||
|
def __init__(self):
|
||||||
|
FSM.FSM.__init__(self, 'eyes')
|
||||||
|
|
||||||
|
self.__unblinkName = "unblink"
|
||||||
|
|
||||||
|
# Eyes are initially open.
|
||||||
|
self.request('Open')
|
||||||
|
|
||||||
|
def defaultFilter(self, request, args):
|
||||||
|
# The default filter accepts any direct state request (these
|
||||||
|
# start with a capital letter).
|
||||||
|
if request[0] in string.uppercase:
|
||||||
|
return request
|
||||||
|
|
||||||
|
# Unexpected command requests are quietly ignored.
|
||||||
|
return None
|
||||||
|
|
||||||
|
def enterOpen(self, oldState, newState):
|
||||||
|
print "swap in eyes open model"
|
||||||
|
|
||||||
|
def filterOpen(self, request, args):
|
||||||
|
if request == 'blink':
|
||||||
|
taskMgr.remove(self.__unblinkName)
|
||||||
|
taskMgr.doMethodLater(0.125, self.__unblink, self.__unblinkName)
|
||||||
|
return 'Closed'
|
||||||
|
return self.defaultFilter(request, args)
|
||||||
|
|
||||||
|
def __unblink(self, task):
|
||||||
|
self.request('unblink')
|
||||||
|
return Task.done
|
||||||
|
|
||||||
|
def enterClosed(self, oldState, newState):
|
||||||
|
print "swap in eyes closed model"
|
||||||
|
|
||||||
|
def filterClosed(self, request, args):
|
||||||
|
if request == 'unblink':
|
||||||
|
return 'Open'
|
||||||
|
return self.defaultFilter(request, args)
|
||||||
|
|
||||||
|
def enterSurprised(self, oldState, newState):
|
||||||
|
print "swap in eyes surprised model"
|
||||||
|
|
||||||
|
def enterOff(self, oldState, newState):
|
||||||
|
taskMgr.remove(self.__unblinkName)
|
||||||
|
|
||||||
|
|
||||||
|
####
|
||||||
|
#### Example of using ClassicStyle:
|
||||||
|
##
|
||||||
|
## >>> import SampleFSM
|
||||||
|
## >>> foo = SampleFSM.ClassicStyle('foo')
|
||||||
|
## >>> foo.request('Red')
|
||||||
|
## enterRed(self, 'Off', 'Red')
|
||||||
|
## 'Red'
|
||||||
|
## >>> foo.request('Yellow')
|
||||||
|
## Traceback (most recent call last):
|
||||||
|
## File "<stdin>", line 1, in ?
|
||||||
|
## File "/home/drose/player/direct/src/fsm/FSM.py", line 168, in request
|
||||||
|
## result = func(request, args)
|
||||||
|
## File "/home/drose/player/direct/src/fsm/FSM.py", line 210, in defaultFilter
|
||||||
|
## self.notify.error("%s rejecting request %s from state %s." % (self.name, request, self.state))
|
||||||
|
## File "/home/drose/player/direct/src/directnotify/Notifier.py", line 99, in error
|
||||||
|
## raise exception(errorString)
|
||||||
|
## StandardError: foo rejecting request Yellow from state Red.
|
||||||
|
## >>> foo.request('Green')
|
||||||
|
## exitRed(self, 'Red', 'Green')
|
||||||
|
## enterGreen(self, 'Red', 'Green')
|
||||||
|
## 'Green'
|
||||||
|
## >>>
|
||||||
|
|
||||||
|
####
|
||||||
|
#### Example of using NewStyle:
|
||||||
|
##
|
||||||
|
## >>> import SampleFSM
|
||||||
|
## >>> foo = SampleFSM.NewStyle('foo')
|
||||||
|
## >>> foo.request('Red')
|
||||||
|
## enterRed(self, 'Off', 'Red')
|
||||||
|
## 'Red'
|
||||||
|
## >>> foo.request('advance')
|
||||||
|
## filterRed(self, 'advance', ())
|
||||||
|
## exitRed(self, 'Red', 'Green')
|
||||||
|
## enterGreen(self, 'Red', 'Green')
|
||||||
|
## 'Green'
|
||||||
|
## >>> foo.request('advance')
|
||||||
|
## filterGreen(self, 'advance', ())
|
||||||
|
## exitGreen(self, 'Green', 'Yellow')
|
||||||
|
## enterYellow(self, 'Green', 'Yellow')
|
||||||
|
## 'Yellow'
|
||||||
|
## >>> foo.request('advance')
|
||||||
|
## filterYellow(self, 'advance', ())
|
||||||
|
## exitYellow(self, 'Yellow', 'Red')
|
||||||
|
## enterRed(self, 'Yellow', 'Red')
|
||||||
|
## 'Red'
|
||||||
|
## >>> foo.request('advance')
|
||||||
|
## filterRed(self, 'advance', ())
|
||||||
|
## exitRed(self, 'Red', 'Green')
|
||||||
|
## enterGreen(self, 'Red', 'Green')
|
||||||
|
## 'Green'
|
||||||
|
## >>>
|
||||||
|
|
||||||
|
####
|
||||||
|
#### Example of using ToonEyes:
|
||||||
|
##
|
||||||
|
## >>> from ShowBaseGlobal import *
|
||||||
|
## >>> import SampleFSM
|
||||||
|
## >>> eyes = SampleFSM.ToonEyes()
|
||||||
|
## swap in eyes open model
|
||||||
|
## >>> eyes.request('blink')
|
||||||
|
## swap in eyes closed model
|
||||||
|
## 'Closed'
|
||||||
|
## >>> run()
|
||||||
|
## swap in eyes open model
|
||||||
|
## >>> eyes.request('Surprised')
|
||||||
|
## swap in eyes surprised model
|
||||||
|
## 'Surprised'
|
||||||
|
## >>> eyes.request('blink')
|
||||||
|
## >>>
|
@ -5,7 +5,7 @@ from DirectObject import *
|
|||||||
import types
|
import types
|
||||||
|
|
||||||
# This gets set by a dconfig variable in ShowBase.py
|
# This gets set by a dconfig variable in ShowBase.py
|
||||||
# We cannot put a dconfig in here because FSM is not
|
# We cannot put a dconfig in here because ClassicFSM is not
|
||||||
# dependent on Panda
|
# dependent on Panda
|
||||||
FsmRedefine = 0
|
FsmRedefine = 0
|
||||||
|
|
||||||
@ -193,20 +193,20 @@ class State(DirectObject):
|
|||||||
return(self.__FSMList)
|
return(self.__FSMList)
|
||||||
|
|
||||||
def setChildren(self, FSMList):
|
def setChildren(self, FSMList):
|
||||||
"""setChildren(self, FSM[])
|
"""setChildren(self, ClassicFSM[])
|
||||||
Set the children to given list of FSMs"""
|
Set the children to given list of FSMs"""
|
||||||
self.__FSMList = FSMList
|
self.__FSMList = FSMList
|
||||||
|
|
||||||
def addChild(self, FSM):
|
def addChild(self, ClassicFSM):
|
||||||
"""addChild(self, FSM)
|
"""addChild(self, ClassicFSM)
|
||||||
Add the given FSM to list of child FSMs"""
|
Add the given ClassicFSM to list of child FSMs"""
|
||||||
self.__FSMList.append(FSM)
|
self.__FSMList.append(ClassicFSM)
|
||||||
|
|
||||||
def removeChild(self, FSM):
|
def removeChild(self, ClassicFSM):
|
||||||
"""removeChild(self, FSM)
|
"""removeChild(self, ClassicFSM)
|
||||||
Remove the given FSM from list of child FSMs"""
|
Remove the given ClassicFSM from list of child FSMs"""
|
||||||
if FSM in self.__FSMList:
|
if ClassicFSM in self.__FSMList:
|
||||||
self.__FSMList.remove(FSM)
|
self.__FSMList.remove(ClassicFSM)
|
||||||
|
|
||||||
def hasChildren(self):
|
def hasChildren(self):
|
||||||
"""hasChildren(self)
|
"""hasChildren(self)
|
||||||
|
@ -8,7 +8,7 @@ import DirectNotifyGlobal
|
|||||||
class StateData(DirectObject):
|
class StateData(DirectObject):
|
||||||
"""
|
"""
|
||||||
A StateData is a base class for a single state within a Finite
|
A StateData is a base class for a single state within a Finite
|
||||||
State Machine (FSM).
|
State Machine (ClassicFSM).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
notify = DirectNotifyGlobal.directNotify.newCategory('StateData')
|
notify = DirectNotifyGlobal.directNotify.newCategory('StateData')
|
||||||
|
@ -12,7 +12,7 @@ from ClockDelta import *
|
|||||||
|
|
||||||
import ToontownGlobals
|
import ToontownGlobals
|
||||||
import DirectNotifyGlobal
|
import DirectNotifyGlobal
|
||||||
import FSM
|
import ClassicFSM
|
||||||
#import DistributedInteractiveEntity
|
#import DistributedInteractiveEntity
|
||||||
import DelayDelete
|
import DelayDelete
|
||||||
import Localizer
|
import Localizer
|
||||||
|
@ -5,7 +5,7 @@ from ShowBaseGlobal import *
|
|||||||
from ClockDelta import *
|
from ClockDelta import *
|
||||||
|
|
||||||
import DirectNotifyGlobal
|
import DirectNotifyGlobal
|
||||||
import FSM
|
import ClassicFSM
|
||||||
import DistributedEntity
|
import DistributedEntity
|
||||||
|
|
||||||
class DistributedInteractiveEntity(DistributedEntity.DistributedEntity):
|
class DistributedInteractiveEntity(DistributedEntity.DistributedEntity):
|
||||||
@ -22,7 +22,7 @@ class DistributedInteractiveEntity(DistributedEntity.DistributedEntity):
|
|||||||
DistributedEntity.DistributedEntity.__init__(self, cr)
|
DistributedEntity.DistributedEntity.__init__(self, cr)
|
||||||
assert(self.debugPrint("DistributedInteractiveEntity()"))
|
assert(self.debugPrint("DistributedInteractiveEntity()"))
|
||||||
|
|
||||||
self.fsm = FSM.FSM('DistributedInteractiveEntity',
|
self.fsm = ClassicFSM.ClassicFSM('DistributedInteractiveEntity',
|
||||||
[State.State('off',
|
[State.State('off',
|
||||||
self.enterOff,
|
self.enterOff,
|
||||||
self.exitOff,
|
self.exitOff,
|
||||||
|
@ -7,7 +7,7 @@ from AIBaseGlobal import *
|
|||||||
from ClockDelta import *
|
from ClockDelta import *
|
||||||
|
|
||||||
import DirectNotifyGlobal
|
import DirectNotifyGlobal
|
||||||
import FSM
|
import ClassicFSM
|
||||||
import DistributedEntityAI
|
import DistributedEntityAI
|
||||||
import State
|
import State
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ class DistributedInteractiveEntityAI(DistributedEntityAI.DistributedEntityAI):
|
|||||||
assert(self.debugPrint(
|
assert(self.debugPrint(
|
||||||
"DistributedInteractiveEntityAI(entId=%s)"
|
"DistributedInteractiveEntityAI(entId=%s)"
|
||||||
%(entId)))
|
%(entId)))
|
||||||
self.fsm = FSM.FSM('DistributedInteractiveEntityAI',
|
self.fsm = ClassicFSM.ClassicFSM('DistributedInteractiveEntityAI',
|
||||||
[State.State('off',
|
[State.State('off',
|
||||||
self.enterOff,
|
self.enterOff,
|
||||||
self.exitOff,
|
self.exitOff,
|
||||||
|
@ -21,7 +21,7 @@ import math
|
|||||||
import sys
|
import sys
|
||||||
import Loader
|
import Loader
|
||||||
import time
|
import time
|
||||||
import FSM
|
import ClassicFSM
|
||||||
import State
|
import State
|
||||||
import DirectObject
|
import DirectObject
|
||||||
import SfxPlayer
|
import SfxPlayer
|
||||||
|
@ -10,7 +10,7 @@ DELTA = (5.0 / 360.) * 2.0 * math.pi
|
|||||||
|
|
||||||
class FSMInspector(AppShell):
|
class FSMInspector(AppShell):
|
||||||
# Override class variables
|
# Override class variables
|
||||||
appname = 'FSM Inspector'
|
appname = 'ClassicFSM Inspector'
|
||||||
frameWidth = 400
|
frameWidth = 400
|
||||||
frameHeight = 450
|
frameHeight = 450
|
||||||
usecommandarea = 0
|
usecommandarea = 0
|
||||||
@ -44,23 +44,23 @@ class FSMInspector(AppShell):
|
|||||||
interior = self.interior()
|
interior = self.interior()
|
||||||
menuBar = self.menuBar
|
menuBar = self.menuBar
|
||||||
|
|
||||||
# FSM Menu
|
# ClassicFSM Menu
|
||||||
menuBar.addmenu('FSM', 'FSM Operations')
|
menuBar.addmenu('ClassicFSM', 'ClassicFSM Operations')
|
||||||
menuBar.addmenuitem('FSM', 'command',
|
menuBar.addmenuitem('ClassicFSM', 'command',
|
||||||
'Input grid spacing',
|
'Input grid spacing',
|
||||||
label = 'Grid spacing...',
|
label = 'Grid spacing...',
|
||||||
command = self.popupGridDialog)
|
command = self.popupGridDialog)
|
||||||
# Create the checkbutton variable
|
# Create the checkbutton variable
|
||||||
self._fGridSnap = IntVar()
|
self._fGridSnap = IntVar()
|
||||||
self._fGridSnap.set(1)
|
self._fGridSnap.set(1)
|
||||||
menuBar.addmenuitem('FSM', 'checkbutton',
|
menuBar.addmenuitem('ClassicFSM', 'checkbutton',
|
||||||
'Enable/disable grid',
|
'Enable/disable grid',
|
||||||
label = 'Snap to grid',
|
label = 'Snap to grid',
|
||||||
variable = self._fGridSnap,
|
variable = self._fGridSnap,
|
||||||
command = self.toggleGridSnap)
|
command = self.toggleGridSnap)
|
||||||
menuBar.addmenuitem('FSM', 'command',
|
menuBar.addmenuitem('ClassicFSM', 'command',
|
||||||
'Print out FSM layout',
|
'Print out ClassicFSM layout',
|
||||||
label = 'Print FSM layout',
|
label = 'Print ClassicFSM layout',
|
||||||
command = self.printLayout)
|
command = self.printLayout)
|
||||||
|
|
||||||
# States Menu
|
# States Menu
|
||||||
@ -238,7 +238,7 @@ class FSMInspector(AppShell):
|
|||||||
self.stateInspectorDict[key].setGridSize(size)
|
self.stateInspectorDict[key].setGridSize(size)
|
||||||
|
|
||||||
def popupGridDialog(self):
|
def popupGridDialog(self):
|
||||||
spacing = askstring('FSM Grid Spacing', 'Grid Spacing:')
|
spacing = askstring('ClassicFSM Grid Spacing', 'Grid Spacing:')
|
||||||
if spacing:
|
if spacing:
|
||||||
self.setGridSize(spacing)
|
self.setGridSize(spacing)
|
||||||
self._gridSize = spacing
|
self._gridSize = spacing
|
||||||
@ -253,7 +253,7 @@ class FSMInspector(AppShell):
|
|||||||
dict = self.stateInspectorDict
|
dict = self.stateInspectorDict
|
||||||
keys = dict.keys()
|
keys = dict.keys()
|
||||||
keys.sort
|
keys.sort
|
||||||
print "FSM.FSM('%s', [" % self.name
|
print "ClassicFSM.ClassicFSM('%s', [" % self.name
|
||||||
for key in keys[:-1]:
|
for key in keys[:-1]:
|
||||||
si = dict[key]
|
si = dict[key]
|
||||||
center = si.center()
|
center = si.center()
|
||||||
@ -282,7 +282,7 @@ class FSMInspector(AppShell):
|
|||||||
self.balloon.configure(state = 'none')
|
self.balloon.configure(state = 'none')
|
||||||
|
|
||||||
def onDestroy(self, event):
|
def onDestroy(self, event):
|
||||||
""" Called on FSM Panel shutdown """
|
""" Called on ClassicFSM Panel shutdown """
|
||||||
self.fsm.inspecting = 0
|
self.fsm.inspecting = 0
|
||||||
for si in self.stateInspectorDict.values():
|
for si in self.stateInspectorDict.values():
|
||||||
self.ignore(self.name + '_' + si.getName() + '_entered')
|
self.ignore(self.name + '_' + si.getName() + '_entered')
|
||||||
@ -454,7 +454,7 @@ want-tk #t
|
|||||||
|
|
||||||
from ShowBaseGlobal import *
|
from ShowBaseGlobal import *
|
||||||
|
|
||||||
import FSM
|
import ClassicFSM
|
||||||
import State
|
import State
|
||||||
|
|
||||||
def enterState():
|
def enterState():
|
||||||
@ -463,7 +463,7 @@ def enterState():
|
|||||||
def exitState():
|
def exitState():
|
||||||
print 'exitState'
|
print 'exitState'
|
||||||
|
|
||||||
fsm = FSM.FSM('stopLight',
|
fsm = ClassicFSM.ClassicFSM('stopLight',
|
||||||
[ State.State('red', enterState, exitState, ['green']),
|
[ State.State('red', enterState, exitState, ['green']),
|
||||||
State.State('yellow', enterState, exitState, ['red']),
|
State.State('yellow', enterState, exitState, ['red']),
|
||||||
State.State('green', enterState, exitState, ['yellow']) ],
|
State.State('green', enterState, exitState, ['yellow']) ],
|
||||||
@ -476,7 +476,7 @@ inspector = FSMInspector.FSMInspector(fsm, title = fsm.getName())
|
|||||||
|
|
||||||
# Note, the inspectorPos argument is optional, the inspector will
|
# Note, the inspectorPos argument is optional, the inspector will
|
||||||
# automagically position states on startup
|
# automagically position states on startup
|
||||||
fsm = FSM.FSM('stopLight', [
|
fsm = ClassicFSM.ClassicFSM('stopLight', [
|
||||||
State.State('yellow',
|
State.State('yellow',
|
||||||
enterState,
|
enterState,
|
||||||
exitState,
|
exitState,
|
||||||
@ -510,8 +510,8 @@ Features:
|
|||||||
- Middle mouse button will grab the canvas and slide things around
|
- Middle mouse button will grab the canvas and slide things around
|
||||||
if your state machine is bigger than the viewing area
|
if your state machine is bigger than the viewing area
|
||||||
- There are some self explanatory menu options up at the top, the most
|
- There are some self explanatory menu options up at the top, the most
|
||||||
useful being: "print FSM layout" which will print out python code
|
useful being: "print ClassicFSM layout" which will print out python code
|
||||||
which will create an FSM augmented with layout information for the
|
which will create an ClassicFSM augmented with layout information for the
|
||||||
viewer so everything shows up in the same place the next time you
|
viewer so everything shows up in the same place the next time you
|
||||||
inspect the state machine
|
inspect the state machine
|
||||||
|
|
||||||
|
@ -408,7 +408,7 @@ class InspectorWindow:
|
|||||||
part = self.topInspector().partNumber(partNumber)
|
part = self.topInspector().partNumber(partNumber)
|
||||||
print part
|
print part
|
||||||
from PandaModules import NodePath
|
from PandaModules import NodePath
|
||||||
import FSM
|
import ClassicFSM
|
||||||
popupMenu = None
|
popupMenu = None
|
||||||
if isinstance(part, NodePath):
|
if isinstance(part, NodePath):
|
||||||
popupMenu = self.createPopupMenu(
|
popupMenu = self.createPopupMenu(
|
||||||
@ -416,11 +416,11 @@ class InspectorWindow:
|
|||||||
[('Explore', NodePath.explore),
|
[('Explore', NodePath.explore),
|
||||||
('Place', NodePath.place),
|
('Place', NodePath.place),
|
||||||
('Set Color', NodePath.rgbPanel)])
|
('Set Color', NodePath.rgbPanel)])
|
||||||
elif isinstance(part, FSM.FSM):
|
elif isinstance(part, ClassicFSM.ClassicFSM):
|
||||||
import FSMInspector
|
import FSMInspector
|
||||||
popupMenu = self.createPopupMenu(
|
popupMenu = self.createPopupMenu(
|
||||||
part,
|
part,
|
||||||
[('Inspect FSM', FSMInspector.FSMInspector)])
|
[('Inspect ClassicFSM', FSMInspector.FSMInspector)])
|
||||||
print popupMenu
|
print popupMenu
|
||||||
if popupMenu:
|
if popupMenu:
|
||||||
popupMenu.post(event.widget.winfo_pointerx(),
|
popupMenu.post(event.widget.winfo_pointerx(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user