introduce demand()

This commit is contained in:
David Rose 2005-01-12 22:30:10 +00:00
parent 824a81cc6c
commit c91af76209

View File

@ -5,9 +5,19 @@ previously called FSM.py (now called ClassicFSM.py).
from direct.showbase import DirectObject from direct.showbase import DirectObject
from direct.directnotify import DirectNotifyGlobal from direct.directnotify import DirectNotifyGlobal
from direct.showbase import PythonUtil
import types import types
import string import string
class FSMException(Exception):
pass
class AlreadyInTransition(FSMException):
pass
class RequestDenied(FSMException):
pass
class FSM(DirectObject.DirectObject): class FSM(DirectObject.DirectObject):
""" """
A Finite State Machine. This is intended to be the base class A Finite State Machine. This is intended to be the base class
@ -130,6 +140,11 @@ class FSM(DirectObject.DirectObject):
# must be approved by some filter function. # must be approved by some filter function.
self.defaultTransitions = None self.defaultTransitions = None
# This member records transition requests made by demand() or
# forceTransition() while the FSM is in transition between
# states.
self.__requestQueue = []
def __del__(self): def __del__(self):
self.cleanup() self.cleanup()
@ -148,21 +163,54 @@ class FSM(DirectObject.DirectObject):
return self.state return self.state
return self.newState return self.newState
def forceTransition(self, newState): def forceTransition(self, request, *args):
"""Changes unconditionally to the indicated state. This """Changes unconditionally to the indicated state. This
bypasses the filterState() function, and just calls bypasses the filterState() function, and just calls
exitState() followed by enterState().""" exitState() followed by enterState()."""
assert(isinstance(newState, types.StringTypes)) assert(isinstance(request, types.StringTypes))
self.notify.debug("%s.forceTransition(%s, %s" % (self.name, request, str(args)[1:]))
self.__setState(newState) if not self.state:
# Queue up the request.
self.__requestQueue.append(PythonUtil.Functor(self.forceTransition, request, *args))
return
self.__setState(request, *args)
def demand(self, request, *args):
"""Requests a state transition, by code that does not expect
the request to be denied. If the request is denied, raises a
RequestDenied exception.
Unlike request(), this method allows a new request to be made
while the FSM is currently in transition. In this case, the
request is queued up and will be executed when the current
transition finishes. Multiple requests will queue up in
sequence.
"""
assert(isinstance(request, types.StringTypes))
self.notify.debug("%s.demand(%s, %s" % (self.name, request, str(args)[1:]))
if not self.state:
# Queue up the request.
self.__requestQueue.append(PythonUtil.Functor(self.demand, request, *args))
return
if not self.request(request, *args):
raise RequestDenied, request
def request(self, request, *args): def request(self, request, *args):
"""Requests a state transition (or other behavior). The request """Requests a state transition (or other behavior). The
parameter should be a string. The request, along with any request may be denied by the FSM's filter function. If it is
additional arguments, is passed to the current filterState() denied, the filter function may either raise an exception
function. If filterState() returns a string, the FSM (RequestDenied), or it may simply return None, without
transitions to that state. changing the FSM's state.
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 The return value is the same as the return value of
filterState() (that is, None if the request does not provoke a filterState() (that is, None if the request does not provoke a
@ -170,15 +218,17 @@ class FSM(DirectObject.DirectObject):
of the state followed by any optional args.) of the state followed by any optional args.)
If the FSM is currently in transition (i.e. in the middle of If the FSM is currently in transition (i.e. in the middle of
executing an enterState or exitState function), the request is executing an enterState or exitState function), an
denied and None is returned.""" AlreadyInTransition exception is raised (but see demand(),
which will queue these requests up and apply when the
transition is complete)."""
assert(isinstance(request, types.StringTypes)) assert(isinstance(request, types.StringTypes))
self.notify.debug("%s.request(%s, %s" % (self.name, request, str(args)[1:])) self.notify.debug("%s.request(%s, %s" % (self.name, request, str(args)[1:]))
if not self.state: if not self.state:
self.notify.warning("rejecting request %s while FSM is in transition from %s to %s." % (request, self.oldState, self.newState)) error = "requested %s while FSM is in transition from %s to %s." % (request, self.oldState, self.newState)
return None raise AlreadyInTransition, error
func = getattr(self, "filter" + self.state, None) func = getattr(self, "filter" + self.state, None)
if not func: if not func:
@ -244,7 +294,7 @@ class FSM(DirectObject.DirectObject):
# request) not listed in defaultTransitions and not # request) not listed in defaultTransitions and not
# handled by an earlier filter. # handled by an earlier filter.
if request[0] in string.uppercase: if request[0] in string.uppercase:
self.notify.error("%s rejecting request %s from state %s." % (self.name, request, self.state)) raise RequestedDenied, request
# In either case, we quietly ignore unhandled command # In either case, we quietly ignore unhandled command
# (lowercase) requests. # (lowercase) requests.
@ -296,6 +346,10 @@ class FSM(DirectObject.DirectObject):
del self.oldState del self.oldState
del self.newState del self.newState
if self.__requestQueue:
request = self.__requestQueue.pop(0)
assert(self.notify.debug("%s continued queued request." % (self.name)))
request()
def __callTransitionFunc(self, name, *args): def __callTransitionFunc(self, name, *args):
# Calls the appropriate enter or exit function when # Calls the appropriate enter or exit function when