Browse Source

FunctionCall support for positional and keyword arguments

Darren Ranalli 17 years ago
parent
commit
d69414689f
1 changed files with 119 additions and 11 deletions
  1. 119 11
      direct/src/fsm/StatePush.py

+ 119 - 11
direct/src/fsm/StatePush.py

@@ -5,6 +5,7 @@ __all__ = ['StateVar', 'FunctionCall', 'EnterExit', 'Pulse', 'EventPulse',
            'EventArgument', ]
 
 from direct.showbase.DirectObject import DirectObject
+import types
 
 class PushesStateChanges:
     # base class for objects that broadcast state changes to a set of subscriber objects
@@ -141,20 +142,108 @@ if __debug__:
     del scn
     del sv
 
-class FunctionCall(StateChangeNode):
-    # calls func with new state whenever state changes
-    def __init__(self, source, func):
+class ReceivesMultipleStateChanges:
+    # base class for objects that subscribe to state changes from multiple PushesStateChanges
+    # objects
+    def __init__(self):
+        self._key2source = {}
+        self._source2key = {}
+
+    def destroy(self):
+        keys = self._key2source.keys()
+        for key in keys:
+            self._unsubscribe(key)
+        del self._key2source
+        del self._source2key
+
+    def _subscribeTo(self, source, key):
+        self._unsubscribe(key)
+        self._key2source[key] = source
+        self._source2key[source] = key
+        source._addSubscription(self)
+
+    def _unsubscribe(self, key):
+        if key in self._key2source:
+            source = self._key2source[key]
+            source._removeSubscription(self)
+            del self._key2source[key]
+            del self._source2key[source]
+
+    def _recvStatePush(self, source):
+        self._recvMultiStatePush(self._source2key[source], source)
+
+    def _recvMultiStatePush(self, key, source):
+        pass
+
+if __debug__:
+    rsc = ReceivesMultipleStateChanges()
+    sv = StateVar(0)
+    sv2 = StateVar('b')
+    rsc._subscribeTo(sv, 'a')
+    rsc._subscribeTo(sv2, 2)
+    rsc._unsubscribe('a')
+    rsc.destroy()
+    del rsc
+
+class FunctionCall(ReceivesMultipleStateChanges, PushesStateChanges):
+    # calls func with provided args whenever arguments' state changes
+    def __init__(self, func, *args, **kArgs):
+        self._initialized = False
+        ReceivesMultipleStateChanges.__init__(self)
+        PushesStateChanges.__init__(self, None)
         self._func = func
-        StateChangeNode.__init__(self, source)
+        self._args = args
+        self._kArgs = kArgs
+        # keep a copy of the arguments ready to go, already filled in with
+        # the value of arguments that push state
+        self._bakedArgs = []
+        self._bakedKargs = {}
+        for i in xrange(len(self._args)):
+            key = i
+            arg = self._args[i]
+            if isinstance(arg, PushesStateChanges):
+                self._bakedArgs.append(arg.getState())
+                self._subscribeTo(arg, key)
+            else:
+                self._bakedArgs.append(self._args[i])
+        for key, arg in self._kArgs.iteritems():
+            if isinstance(arg, PushesStateChanges):
+                self._bakedKargs[key] = arg.getState()
+                self._subscribeTo(arg, key)
+            else:
+                self._bakedKargs[key] = arg
+        self._initialized = True
+        # push the current state to any listeners
         self._handleStateChange()
 
     def destroy(self):
-        StateChangeNode.destroy(self)
+        ReceivesMultipleStateChanges.destroy(self)
+        PushesStateChanges.destroy(self)
         del self._func
+        del self._args
+        del self._kArgs
+        del self._bakedArgs
+        del self._bakedKargs
+
+    def getState(self):
+        # for any state recievers that are hooked up to us, they get a tuple
+        # of (tuple(positional argument values), dict(keyword argument name->value))
+        return (tuple(self._bakedArgs), dict(self._bakedKargs))
+
+    def _recvMultiStatePush(self, key, source):
+        # one of the arguments changed
+        # pick up the new value
+        if isinstance(key, types.StringType):
+            self._bakedKargs[key] = source.getState()
+        else:
+            self._bakedArgs[key] = source.getState()
+        # and send it out
+        self._handlePotentialStateChange(self.getState())
 
     def _handleStateChange(self):
-        self._func(self._value)
-        StateChangeNode._handleStateChange(self)
+        if self._initialized:
+            self._func(*self._bakedArgs, **self._bakedKargs)
+            PushesStateChanges._handleStateChange(self)
         
 if __debug__:
     l = []
@@ -162,7 +251,7 @@ if __debug__:
         l.append(value)
     assert l == []
     sv = StateVar(0)
-    fc = FunctionCall(sv, handler)
+    fc = FunctionCall(handler, sv)
     assert l == [0,]
     sv.set(1)
     assert l == [0,1,]
@@ -175,6 +264,25 @@ if __debug__:
     del handler
     del l
 
+    l = []
+    def handler(value, kDummy=None, kValue=None, l=l):
+        l.append((value, kValue))
+    assert l == []
+    sv = StateVar(0)
+    ksv = StateVar('a')
+    fc = FunctionCall(handler, sv, kValue=ksv)
+    assert l == [(0,'a',),]
+    sv.set(1)
+    assert l == [(0,'a'),(1,'a'),]
+    ksv.set('b')
+    assert l == [(0,'a'),(1,'a'),(1,'b'),]
+    fc.destroy()
+    sv.destroy()
+    del fc
+    del sv
+    del handler
+    del l
+
 class EnterExit(StateChangeNode):
     # call enterFunc when our state becomes true, exitFunc when it becomes false
     def __init__(self, source, enterFunc, exitFunc):
@@ -240,7 +348,7 @@ if __debug__:
     def handler(value, l=l):
         l.append(value)
     p = Pulse()
-    fc = FunctionCall(p, handler)
+    fc = FunctionCall(handler, p)
     assert l == [False, ]
     p.sendPulse()
     assert l == [False, True, False, ]
@@ -268,7 +376,7 @@ if __debug__:
     def handler(value, l=l):
         l.append(value)
     ep = EventPulse('testEvent')
-    fc = FunctionCall(ep, handler)
+    fc = FunctionCall(handler, ep)
     assert l == [False, ]
     messenger.send('testEvent')
     assert l == [False, True, False, ]
@@ -301,7 +409,7 @@ if __debug__:
     def handler(value, l=l):
         l.append(value)
     ea = EventArgument('testEvent', index=1)
-    fc = FunctionCall(ea, handler)
+    fc = FunctionCall(handler, ea)
     messenger.send('testEvent', ['a', 'b'])
     assert l == [None, 'b', ]
     messenger.send('testEvent', [1, 2, 3, ])