rename FSM -> ClassicFSM, introduce new FSM code

This commit is contained in:
David Rose 2004-02-27 17:59:58 +00:00
parent 193984d955
commit 13437d3ff5
13 changed files with 837 additions and 378 deletions

View 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)

View File

@ -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 string
class FSM(DirectObject):
"""FSM class: Finite State Machine class"""
class FSM(DirectObject.DirectObject):
"""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
notify = directNotify.newCategory("FSM")
The states of an FSM are defined implicitly. Each state is
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
# 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 enterRed(self, oldState, newState):
... do stuff ...
def __init__(self, name, states=[], initialStateName=None,
finalStateName=None, onUndefTransition=DISALLOW_VERBOSE):
"""__init__(self, string, State[], string, string, int)
def exitRed(self, oldState, newState):
... cleanup stuff ...
FSM constructor: takes name, list of states, initial state and
final state as:
def enterYellow(self, oldState, newState):
... do stuff ...
fsm = FSM.FSM('stopLight',
[ State.State('red', enterRed, exitRed, ['green']),
State.State('yellow', enterYellow, exitYellow, ['red']),
State.State('green', enterGreen, exitGreen, ['yellow']) ],
'red',
'red')
def exitYellow(self, oldState, newState):
... cleanup stuff ...
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
def enterGreen(self, oldState, newState):
... do stuff ...
'onUndefTransition' flag determines behavior when undefined
transition is requested; see flag definitions above
def exitGreen(self, oldState, newState):
... 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)
self.setStates(states)
self.setInitialState(initialStateName)
self.setFinalState(finalStateName)
Both functions are optional. If either function is omitted, the
state is still defined, but nothing is done when transitioning
into (or out of) the state.
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
self.inspecting = 0
def filterRed(self, request, args):
if request in ['Green']:
return request
return None
# We do not enter the initial state to separate
# construction from activation
self.__currentState = None
def filterYellow(self, request, args):
if request in ['Red']:
return request
return 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
def filterGreen(self, request, args):
if request in ['Yellow']:
return request
return None
# I know this isn't how __repr__ is supposed to be used, but it
# is nice and convenient.
As above, the filterState() functions are optional. If any is
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):
return self.__str__()
@ -70,290 +250,8 @@ class FSM(DirectObject):
"""__str__(self)
Print out something useful about the fsm
"""
currentState = self.getCurrentState()
if currentState:
str = ("FSM " + self.getName() + ' in state "' +
currentState.getName() + '"')
if self.state:
str = ("FSM " + self.name + ' in state "' + self.state + '"')
else:
str = ("FSM " + self.getName() + ' not in any state')
str = ("FSM " + self.name + ' 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)

View File

@ -2,14 +2,14 @@
import DirectNotifyGlobal
#import DistributedObject
import FSM
import ClassicFSM
import State
import Task
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
is a DistributedObject.
@ -34,7 +34,7 @@ class FourState:
+------+
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
working on, so this base class was created.
@ -120,7 +120,7 @@ class FourState:
[names[1]]),
}
self.stateIndex = 0
self.fsm = FSM.FSM('FourState',
self.fsm = ClassicFSM.ClassicFSM('FourState',
self.states.values(),
# Initial State
names[0],

View File

@ -2,14 +2,14 @@
import DirectNotifyGlobal
#import DistributedObjectAI
import FSM
import ClassicFSM
import State
import Task
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
is a DistributedObjectAI.
@ -34,7 +34,7 @@ class FourStateAI:
+------+
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
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.
The list of duration values should be the same length
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
values of None.
@ -125,7 +125,7 @@ class FourStateAI:
self.exitState4,
[names[1]]),
}
self.fsm = FSM.FSM('FourState',
self.fsm = ClassicFSM.ClassicFSM('FourState',
self.states.values(),
# Initial State
names[0],

193
direct/src/fsm/SampleFSM.py Normal file
View 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')
## >>>

View File

@ -5,7 +5,7 @@ from DirectObject import *
import types
# 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
FsmRedefine = 0
@ -193,20 +193,20 @@ class State(DirectObject):
return(self.__FSMList)
def setChildren(self, FSMList):
"""setChildren(self, FSM[])
"""setChildren(self, ClassicFSM[])
Set the children to given list of FSMs"""
self.__FSMList = FSMList
def addChild(self, FSM):
"""addChild(self, FSM)
Add the given FSM to list of child FSMs"""
self.__FSMList.append(FSM)
def addChild(self, ClassicFSM):
"""addChild(self, ClassicFSM)
Add the given ClassicFSM to list of child FSMs"""
self.__FSMList.append(ClassicFSM)
def removeChild(self, FSM):
"""removeChild(self, FSM)
Remove the given FSM from list of child FSMs"""
if FSM in self.__FSMList:
self.__FSMList.remove(FSM)
def removeChild(self, ClassicFSM):
"""removeChild(self, ClassicFSM)
Remove the given ClassicFSM from list of child FSMs"""
if ClassicFSM in self.__FSMList:
self.__FSMList.remove(ClassicFSM)
def hasChildren(self):
"""hasChildren(self)

View File

@ -8,7 +8,7 @@ import DirectNotifyGlobal
class StateData(DirectObject):
"""
A StateData is a base class for a single state within a Finite
State Machine (FSM).
State Machine (ClassicFSM).
"""
notify = DirectNotifyGlobal.directNotify.newCategory('StateData')

View File

@ -12,7 +12,7 @@ from ClockDelta import *
import ToontownGlobals
import DirectNotifyGlobal
import FSM
import ClassicFSM
#import DistributedInteractiveEntity
import DelayDelete
import Localizer

View File

@ -5,7 +5,7 @@ from ShowBaseGlobal import *
from ClockDelta import *
import DirectNotifyGlobal
import FSM
import ClassicFSM
import DistributedEntity
class DistributedInteractiveEntity(DistributedEntity.DistributedEntity):
@ -22,7 +22,7 @@ class DistributedInteractiveEntity(DistributedEntity.DistributedEntity):
DistributedEntity.DistributedEntity.__init__(self, cr)
assert(self.debugPrint("DistributedInteractiveEntity()"))
self.fsm = FSM.FSM('DistributedInteractiveEntity',
self.fsm = ClassicFSM.ClassicFSM('DistributedInteractiveEntity',
[State.State('off',
self.enterOff,
self.exitOff,

View File

@ -7,7 +7,7 @@ from AIBaseGlobal import *
from ClockDelta import *
import DirectNotifyGlobal
import FSM
import ClassicFSM
import DistributedEntityAI
import State
@ -30,7 +30,7 @@ class DistributedInteractiveEntityAI(DistributedEntityAI.DistributedEntityAI):
assert(self.debugPrint(
"DistributedInteractiveEntityAI(entId=%s)"
%(entId)))
self.fsm = FSM.FSM('DistributedInteractiveEntityAI',
self.fsm = ClassicFSM.ClassicFSM('DistributedInteractiveEntityAI',
[State.State('off',
self.enterOff,
self.exitOff,

View File

@ -21,7 +21,7 @@ import math
import sys
import Loader
import time
import FSM
import ClassicFSM
import State
import DirectObject
import SfxPlayer

View File

@ -10,7 +10,7 @@ DELTA = (5.0 / 360.) * 2.0 * math.pi
class FSMInspector(AppShell):
# Override class variables
appname = 'FSM Inspector'
appname = 'ClassicFSM Inspector'
frameWidth = 400
frameHeight = 450
usecommandarea = 0
@ -44,23 +44,23 @@ class FSMInspector(AppShell):
interior = self.interior()
menuBar = self.menuBar
# FSM Menu
menuBar.addmenu('FSM', 'FSM Operations')
menuBar.addmenuitem('FSM', 'command',
# ClassicFSM Menu
menuBar.addmenu('ClassicFSM', 'ClassicFSM Operations')
menuBar.addmenuitem('ClassicFSM', 'command',
'Input grid spacing',
label = 'Grid spacing...',
command = self.popupGridDialog)
# Create the checkbutton variable
self._fGridSnap = IntVar()
self._fGridSnap.set(1)
menuBar.addmenuitem('FSM', 'checkbutton',
menuBar.addmenuitem('ClassicFSM', 'checkbutton',
'Enable/disable grid',
label = 'Snap to grid',
variable = self._fGridSnap,
command = self.toggleGridSnap)
menuBar.addmenuitem('FSM', 'command',
'Print out FSM layout',
label = 'Print FSM layout',
menuBar.addmenuitem('ClassicFSM', 'command',
'Print out ClassicFSM layout',
label = 'Print ClassicFSM layout',
command = self.printLayout)
# States Menu
@ -238,7 +238,7 @@ class FSMInspector(AppShell):
self.stateInspectorDict[key].setGridSize(size)
def popupGridDialog(self):
spacing = askstring('FSM Grid Spacing', 'Grid Spacing:')
spacing = askstring('ClassicFSM Grid Spacing', 'Grid Spacing:')
if spacing:
self.setGridSize(spacing)
self._gridSize = spacing
@ -253,7 +253,7 @@ class FSMInspector(AppShell):
dict = self.stateInspectorDict
keys = dict.keys()
keys.sort
print "FSM.FSM('%s', [" % self.name
print "ClassicFSM.ClassicFSM('%s', [" % self.name
for key in keys[:-1]:
si = dict[key]
center = si.center()
@ -282,7 +282,7 @@ class FSMInspector(AppShell):
self.balloon.configure(state = 'none')
def onDestroy(self, event):
""" Called on FSM Panel shutdown """
""" Called on ClassicFSM Panel shutdown """
self.fsm.inspecting = 0
for si in self.stateInspectorDict.values():
self.ignore(self.name + '_' + si.getName() + '_entered')
@ -454,7 +454,7 @@ want-tk #t
from ShowBaseGlobal import *
import FSM
import ClassicFSM
import State
def enterState():
@ -463,7 +463,7 @@ def enterState():
def exitState():
print 'exitState'
fsm = FSM.FSM('stopLight',
fsm = ClassicFSM.ClassicFSM('stopLight',
[ State.State('red', enterState, exitState, ['green']),
State.State('yellow', enterState, exitState, ['red']),
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
# automagically position states on startup
fsm = FSM.FSM('stopLight', [
fsm = ClassicFSM.ClassicFSM('stopLight', [
State.State('yellow',
enterState,
exitState,
@ -510,8 +510,8 @@ Features:
- Middle mouse button will grab the canvas and slide things around
if your state machine is bigger than the viewing area
- There are some self explanatory menu options up at the top, the most
useful being: "print FSM layout" which will print out python code
which will create an FSM augmented with layout information for the
useful being: "print ClassicFSM layout" which will print out python code
which will create an ClassicFSM augmented with layout information for the
viewer so everything shows up in the same place the next time you
inspect the state machine

View File

@ -408,7 +408,7 @@ class InspectorWindow:
part = self.topInspector().partNumber(partNumber)
print part
from PandaModules import NodePath
import FSM
import ClassicFSM
popupMenu = None
if isinstance(part, NodePath):
popupMenu = self.createPopupMenu(
@ -416,11 +416,11 @@ class InspectorWindow:
[('Explore', NodePath.explore),
('Place', NodePath.place),
('Set Color', NodePath.rgbPanel)])
elif isinstance(part, FSM.FSM):
elif isinstance(part, ClassicFSM.ClassicFSM):
import FSMInspector
popupMenu = self.createPopupMenu(
part,
[('Inspect FSM', FSMInspector.FSMInspector)])
[('Inspect ClassicFSM', FSMInspector.FSMInspector)])
print popupMenu
if popupMenu:
popupMenu.post(event.widget.winfo_pointerx(),