Browse Source

FSM undefined-transition behavior change

Darren Ranalli 23 years ago
parent
commit
66f53d3764
2 changed files with 92 additions and 24 deletions
  1. 56 18
      direct/src/fsm/FSM.py
  2. 36 6
      direct/src/fsm/State.py

+ 56 - 18
direct/src/fsm/FSM.py

@@ -11,9 +11,15 @@ class FSM(DirectObject):
 
 
     # special methods
     # 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,
     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
         FSM constructor: takes name, list of states, initial state and
         final state as:
         final state as:
@@ -25,6 +31,14 @@ class FSM(DirectObject):
           'red',
           'red',
           '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.setName(name)
@@ -32,6 +46,8 @@ class FSM(DirectObject):
         self.setInitialState(initialStateName)
         self.setInitialState(initialStateName)
         self.setFinalState(finalStateName)
         self.setFinalState(finalStateName)
 
 
+        self.onUndefTransition = onUndefTransition
+
         # Flag to see if we are inspecting
         # Flag to see if we are inspecting
         self.inspecting = 0
         self.inspecting = 0
 
 
@@ -43,7 +59,6 @@ class FSM(DirectObject):
         # should recursively attempt to modify the state while we are
         # should recursively attempt to modify the state while we are
         # doing this.
         # doing this.
         self.__internalStateInFlux = 0
         self.__internalStateInFlux = 0
-        return None
 
 
     # I know this isn't how __repr__ is supposed to be used, but it
     # I know this isn't how __repr__ is supposed to be used, but it
     # is nice and convenient.
     # is nice and convenient.
@@ -71,7 +86,6 @@ class FSM(DirectObject):
         self.__internalStateInFlux = 1
         self.__internalStateInFlux = 1
         self.__enter(self.__initialState, argList)
         self.__enter(self.__initialState, argList)
         assert(not self.__internalStateInFlux)
         assert(not self.__internalStateInFlux)
-        return
 
 
     # Jesse decided that simpler was better with the __str__ function
     # Jesse decided that simpler was better with the __str__ function
     def __str_not__(self):
     def __str_not__(self):
@@ -180,8 +194,10 @@ class FSM(DirectObject):
             self.__internalStateInFlux = 0
             self.__internalStateInFlux = 0
             aState.enter(argList)
             aState.enter(argList)
         else:
         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
             self.__internalStateInFlux = 0
+            FSM.notify.error("[%s]: enter: no such state" % (self.__name))
 
 
     def __transition(self, aState, enterArgList=[], exitArgList=[]):
     def __transition(self, aState, enterArgList=[], exitArgList=[]):
         """__transition(self, State, enterArgList, exitArgList)
         """__transition(self, State, enterArgList, exitArgList)
@@ -225,7 +241,22 @@ class FSM(DirectObject):
             FSM.notify.error("[%s]: request: %s, no such state" %
             FSM.notify.error("[%s]: request: %s, no such state" %
                              (self.__name, aStateName))
                              (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,
             self.__transition(aState,
                               enterArgList,
                               enterArgList,
                               exitArgList)
                               exitArgList)
@@ -254,24 +285,28 @@ class FSM(DirectObject):
                                  (self.__name, aStateName))
                                  (self.__name, aStateName))
             return 0
             return 0
         else:
         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
             return 0
 
 
 
 
     def forceTransition(self, aStateName, enterArgList=[], exitArgList=[]):
     def forceTransition(self, aStateName, enterArgList=[], exitArgList=[]):
-        """ force a transition """
+        """ force a transition -- for debugging ONLY """
         self.request(aStateName, enterArgList, exitArgList, force=1)
         self.request(aStateName, enterArgList, exitArgList, force=1)
 
 
     def conditional_request(self, aStateName, enterArgList=[], exitArgList=[]):
     def conditional_request(self, aStateName, enterArgList=[], exitArgList=[]):
         """request(self, string)
         """request(self, string)
+        'if this transition is defined, do it'
         Attempt transition from currentState to given one, if it exists.
         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
         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
         FSM transitions, letting the same fn be used for different states
         that may not have the same out transitions.
         that may not have the same out transitions.
         """
         """
@@ -295,10 +330,13 @@ class FSM(DirectObject):
             FSM.notify.error("[%s]: request: %s, no such state" %
             FSM.notify.error("[%s]: request: %s, no such state" %
                                 (self.__name, aStateName))
                                 (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)
             return self.request(aStateName, enterArgList, exitArgList)
         else:
         else:
             FSM.notify.debug("[%s]: condition_request: %s, transition doesnt exist" %
             FSM.notify.debug("[%s]: condition_request: %s, transition doesnt exist" %

+ 36 - 6
direct/src/fsm/State.py

@@ -68,14 +68,19 @@ def redefineExitFunc(oldMethod, newFunction):
 
 
 
 
 class State(DirectObject):
 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: """
     """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 = [])
         """__init__(self, string, func, func, string[], inspectorPos = [])
         State constructor: takes name, enter func, exit func, and
         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.__enterFunc = None
         self.__exitFunc = None
         self.__exitFunc = None
 
 
@@ -131,9 +136,29 @@ class State(DirectObject):
         self.redefineFunc(self.__exitFunc, stateExitFunc, ExitFuncRedefineMap)
         self.redefineFunc(self.__exitFunc, stateExitFunc, ExitFuncRedefineMap)
         self.__exitFunc = stateExitFunc
         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):
     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):
     def setTransitions(self, stateTransitions):
         """setTransitions(self, string[])"""
         """setTransitions(self, string[])"""
@@ -141,7 +166,12 @@ class State(DirectObject):
 
 
     def addTransition(self, transition):
     def addTransition(self, transition):
         """addTransitions(self, string)"""
         """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):
     def getInspectorPos(self):
         """getInspectorPos(self)"""
         """getInspectorPos(self)"""