Jelajahi Sumber

FSM undefined-transition behavior change

Darren Ranalli 23 tahun lalu
induk
melakukan
66f53d3764
2 mengubah file dengan 92 tambahan dan 24 penghapusan
  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
 
+    # 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" %

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

@@ -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)"""