FSM undefined-transition behavior change
This commit is contained in:
parent
9be4a24a57
commit
66f53d3764
|
|
@ -11,9 +11,15 @@ class FSM(DirectObject):
|
|||
|
||||
# special methods
|
||||
|
||||
# these are flags that tell the FSM what to do when an
|
||||
# undefined transition is requested
|
||||
ALLOW = 0 # print a warning, and do the transition
|
||||
DISALLOW = 1 # print a warning, and don't do the transition
|
||||
ERROR = 2 # print an error message and raise an exception
|
||||
|
||||
def __init__(self, name, states=[], initialStateName=None,
|
||||
finalStateName=None):
|
||||
"""__init__(self, string, State[], string, string)
|
||||
finalStateName=None, onUndefTransition=ALLOW):
|
||||
"""__init__(self, string, State[], string, string, int)
|
||||
|
||||
FSM constructor: takes name, list of states, initial state and
|
||||
final state as:
|
||||
|
|
@ -25,6 +31,14 @@ class FSM(DirectObject):
|
|||
'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)
|
||||
|
|
@ -32,6 +46,8 @@ class FSM(DirectObject):
|
|||
self.setInitialState(initialStateName)
|
||||
self.setFinalState(finalStateName)
|
||||
|
||||
self.onUndefTransition = onUndefTransition
|
||||
|
||||
# Flag to see if we are inspecting
|
||||
self.inspecting = 0
|
||||
|
||||
|
|
@ -43,7 +59,6 @@ class FSM(DirectObject):
|
|||
# should recursively attempt to modify the state while we are
|
||||
# doing this.
|
||||
self.__internalStateInFlux = 0
|
||||
return None
|
||||
|
||||
# I know this isn't how __repr__ is supposed to be used, but it
|
||||
# is nice and convenient.
|
||||
|
|
@ -71,7 +86,6 @@ class FSM(DirectObject):
|
|||
self.__internalStateInFlux = 1
|
||||
self.__enter(self.__initialState, argList)
|
||||
assert(not self.__internalStateInFlux)
|
||||
return
|
||||
|
||||
# Jesse decided that simpler was better with the __str__ function
|
||||
def __str_not__(self):
|
||||
|
|
@ -180,8 +194,10 @@ class FSM(DirectObject):
|
|||
self.__internalStateInFlux = 0
|
||||
aState.enter(argList)
|
||||
else:
|
||||
FSM.notify.error("[%s]: enter: no such state" % (self.__name))
|
||||
# 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)
|
||||
|
|
@ -225,7 +241,22 @@ class FSM(DirectObject):
|
|||
FSM.notify.error("[%s]: request: %s, no such state" %
|
||||
(self.__name, aStateName))
|
||||
|
||||
if force or (aStateName in self.__currentState.getTransitions()):
|
||||
# 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.
|
||||
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)
|
||||
|
|
@ -254,24 +285,28 @@ class FSM(DirectObject):
|
|||
(self.__name, aStateName))
|
||||
return 0
|
||||
else:
|
||||
if FSM.notify.getWarning():
|
||||
FSM.notify.warning("[%s]: no transition exists from %s to %s" %
|
||||
(self.__name,
|
||||
self.__currentState.getName(),
|
||||
aStateName))
|
||||
msg = ("[%s]: no transition exists from %s to %s" %
|
||||
(self.__name,
|
||||
self.__currentState.getName(),
|
||||
aStateName))
|
||||
if self.onUndefTransition == FSM.ERROR:
|
||||
FSM.notify.error(msg)
|
||||
else:
|
||||
FSM.notify.warning(msg)
|
||||
return 0
|
||||
|
||||
|
||||
def forceTransition(self, aStateName, enterArgList=[], exitArgList=[]):
|
||||
""" force a transition """
|
||||
""" 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 is transition exists to given state,
|
||||
Return true if transition exists to given state,
|
||||
false otherwise. It is NOT an error/warning to attempt a cond_request
|
||||
if the transition doesnt exist. This lets people be sloppy about
|
||||
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.
|
||||
"""
|
||||
|
|
@ -295,10 +330,13 @@ class FSM(DirectObject):
|
|||
FSM.notify.error("[%s]: request: %s, no such state" %
|
||||
(self.__name, aStateName))
|
||||
|
||||
fulltransitionnameset = self.__currentState.getTransitions()
|
||||
fulltransitionnameset.extend([self.__currentState.getName(),self.__finalState.getName()])
|
||||
|
||||
if (aStateName in fulltransitionnameset):
|
||||
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" %
|
||||
|
|
|
|||
|
|
@ -68,14 +68,19 @@ def redefineExitFunc(oldMethod, newFunction):
|
|||
|
||||
|
||||
class State(DirectObject):
|
||||
notify = directNotify.newCategory("State")
|
||||
|
||||
# this 'constant' can be used to specify that the state
|
||||
# can transition to any other state
|
||||
Any = 'ANY'
|
||||
|
||||
"""State class: """
|
||||
|
||||
def __init__(self, name, enterFunc=None, exitFunc=None, transitions=[],
|
||||
inspectorPos = []):
|
||||
def __init__(self, name, enterFunc=None, exitFunc=None,
|
||||
transitions=Any, inspectorPos = []):
|
||||
"""__init__(self, string, func, func, string[], inspectorPos = [])
|
||||
State constructor: takes name, enter func, exit func, and
|
||||
a list of states it can transition to."""
|
||||
a list of states it can transition to (or State.Any)."""
|
||||
self.__enterFunc = None
|
||||
self.__exitFunc = None
|
||||
|
||||
|
|
@ -131,9 +136,29 @@ class State(DirectObject):
|
|||
self.redefineFunc(self.__exitFunc, stateExitFunc, ExitFuncRedefineMap)
|
||||
self.__exitFunc = stateExitFunc
|
||||
|
||||
def transitionsToAny(self):
|
||||
""" returns true if State defines transitions to any other state """
|
||||
return self.__transitions is State.Any
|
||||
|
||||
def getTransitions(self):
|
||||
"""getTransitions(self)"""
|
||||
return(self.__transitions)
|
||||
"""getTransitions(self)
|
||||
warning -- if the state transitions to any other state,
|
||||
returns an empty list (falsely implying that the state
|
||||
has no transitions)
|
||||
see State.transitionsToAny()
|
||||
"""
|
||||
if self.transitionsToAny():
|
||||
return []
|
||||
return self.__transitions
|
||||
|
||||
def isTransitionDefined(self, otherState):
|
||||
if self.transitionsToAny():
|
||||
return 1
|
||||
|
||||
# if we're given a state object, get its name instead
|
||||
if type(otherState) != type(''):
|
||||
otherState = otherState.getName()
|
||||
return (otherState in self.__transitions)
|
||||
|
||||
def setTransitions(self, stateTransitions):
|
||||
"""setTransitions(self, string[])"""
|
||||
|
|
@ -141,7 +166,12 @@ class State(DirectObject):
|
|||
|
||||
def addTransition(self, transition):
|
||||
"""addTransitions(self, string)"""
|
||||
self.__transitions.append(transition)
|
||||
if not self.transitionsToAny():
|
||||
self.__transitions.append(transition)
|
||||
else:
|
||||
State.notify.warning(
|
||||
'attempted to add transition %s to state that '
|
||||
'transitions to any state')
|
||||
|
||||
def getInspectorPos(self):
|
||||
"""getInspectorPos(self)"""
|
||||
|
|
|
|||
Loading…
Reference in New Issue