Browse Source

merged from pirates_1_first_merged_to_trunk

Darren Ranalli 20 years ago
parent
commit
5be710f8b5
35 changed files with 1697 additions and 192 deletions
  1. 0 1
      direct/src/cluster/ClusterClient.py
  2. 29 1
      direct/src/controls/BattleWalker.py
  3. 7 0
      direct/src/controls/ControlManager.py
  4. 137 1
      direct/src/dcparser/dcClass.cxx
  5. 8 1
      direct/src/dcparser/dcClass.h
  6. 11 11
      direct/src/dcparser/dcField.cxx
  7. 1 1
      direct/src/dcparser/dcField.h
  8. 1 1
      direct/src/dcparser/dcFile.cxx
  9. 5 2
      direct/src/directnotify/Notifier.py
  10. 59 11
      direct/src/directtools/DirectCameraControl.py
  11. 13 11
      direct/src/directtools/DirectManipulation.py
  12. 36 28
      direct/src/distributed/AsyncRequest.py
  13. 0 2
      direct/src/distributed/CRCache.py
  14. 132 44
      direct/src/distributed/ClientRepository.py
  15. 102 5
      direct/src/distributed/ConnectionRepository.py
  16. 0 1
      direct/src/distributed/DistributedCartesianGrid.py
  17. 94 0
      direct/src/distributed/DistributedNodeUD.py
  18. 4 1
      direct/src/distributed/DistributedObject.py
  19. 4 0
      direct/src/distributed/DistributedObjectAI.py
  20. 27 0
      direct/src/distributed/DistributedObjectGlobalUD.py
  21. 216 0
      direct/src/distributed/DistributedObjectOV.py
  22. 557 0
      direct/src/distributed/DistributedObjectUD.py
  23. 2 3
      direct/src/distributed/DistributedSmoothNode.py
  24. 48 12
      direct/src/distributed/DoCollectionManager.py
  25. 8 8
      direct/src/distributed/DoInterestManager.py
  26. 4 0
      direct/src/distributed/MsgTypes.py
  27. 6 5
      direct/src/distributed/PyDatagram.py
  28. 11 0
      direct/src/distributed/cConnectionRepository.I
  29. 123 4
      direct/src/distributed/cConnectionRepository.cxx
  30. 5 1
      direct/src/distributed/cConnectionRepository.h
  31. 0 1
      direct/src/gui/DirectScrolledList.py
  32. 0 3
      direct/src/showbase/PythonUtil.py
  33. 36 31
      direct/src/showbase/Transitions.py
  34. 11 1
      direct/src/task/Task.py
  35. 0 1
      direct/src/tkwidgets/AppShell.py

+ 0 - 1
direct/src/cluster/ClusterClient.py

@@ -181,7 +181,6 @@ class ClusterClientSync(ClusterClient):
 
     def startSwapCoordinatorTask(self):
         taskMgr.add(self.swapCoordinator, "clientSwapCoordinator", 51)
-        return None
 
     def swapCoordinator(self,task):
         self.ready = 1

+ 29 - 1
direct/src/controls/BattleWalker.py

@@ -8,6 +8,10 @@ def ToggleStrafe():
     global BattleStrafe
     BattleStrafe = not BattleStrafe
 
+def SetStrafe(status):
+    global BattleStrafe
+    BattleStrafe = status
+    
 class BattleWalker(GravityWalker.GravityWalker):
     def __init__(self):
         GravityWalker.GravityWalker.__init__(self)
@@ -109,7 +113,30 @@ class BattleWalker(GravityWalker.GravityWalker):
         elif delH > rMax:
             self.avatarNodePath.setH(curH+rMax)
             self.rotationSpeed=self.avatarControlRotateSpeed
-            
+
+        # Check to see if we're moving at all:
+        self.moving = self.speed or self.slideSpeed or self.rotationSpeed or (self.priorParent!=Vec3.zero())
+        if self.moving:
+            distance = dt * self.speed
+            slideDistance = dt * self.slideSpeed
+            rotation = dt * self.rotationSpeed
+
+            # Take a step in the direction of our previous heading.
+            self.vel=Vec3(Vec3.forward() * distance +
+                          Vec3.right() * slideDistance)
+            if self.vel != Vec3.zero() or self.priorParent != Vec3.zero():
+                if 1:
+                    # rotMat is the rotation matrix corresponding to
+                    # our previous heading.
+                    rotMat=Mat3.rotateMatNormaxis(self.avatarNodePath.getH(), Vec3.up())
+                    step=(self.priorParent * dt) + rotMat.xform(self.vel)
+                    self.avatarNodePath.setFluidPos(Point3(
+                            self.avatarNodePath.getPos()+step))
+            self.avatarNodePath.setH(self.avatarNodePath.getH()+rotation)
+        else:
+            self.vel.set(0.0, 0.0, 0.0)
+
+        """
         # Check to see if we're moving at all:
         self.moving = self.advanceSpeed or self.slideSpeed or self.rotationSpeed or (self.priorParent!=Vec3.zero())
         if self.moving:
@@ -138,6 +165,7 @@ class BattleWalker(GravityWalker.GravityWalker):
             self.avatarNodePath.setH(self.avatarNodePath.getH()+rotation)
         else:
             self.vel.set(0.0, 0.0, 0.0)
+        """
         if self.moving or jump:
             messenger.send("avatarMoving")
         return Task.cont

+ 7 - 0
direct/src/controls/ControlManager.py

@@ -44,6 +44,13 @@ class ControlManager:
         inputState.watch("reverse", "alt-arrow_down", "arrow_down-up")
         inputState.watch("reverse", "control-alt-arrow_down", "arrow_down-up")
         inputState.watch("reverse", "shift-arrow_down", "arrow_down-up")
+        
+        inputState.watch("reverse", "mouse4", "mouse4-up")
+        inputState.watch("reverse", "control-mouse4", "mouse4-up")
+        inputState.watch("reverse", "shift-control-mouse4", "mouse4-up")
+        inputState.watch("reverse", "alt-mouse4", "mouse4-up")
+        inputState.watch("reverse", "control-alt-mouse4", "mouse4-up")
+        inputState.watch("reverse", "shift-mouse4", "mouse4-up")
 
         inputState.watch("turnLeft", "arrow_left", "arrow_left-up")
         inputState.watch("turnLeft", "control-arrow_left", "arrow_left-up")

+ 137 - 1
direct/src/dcparser/dcClass.cxx

@@ -63,6 +63,7 @@ DCClass(DCFile *dc_file, const string &name, bool is_struct, bool bogus_class) :
       
 #ifdef HAVE_PYTHON
   _class_def = NULL;
+  _owner_class_def = NULL;
 #endif
 }
 
@@ -84,6 +85,7 @@ DCClass::
 
 #ifdef HAVE_PYTHON
   Py_XDECREF(_class_def);
+  Py_XDECREF(_owner_class_def);
 #endif
 }
 
@@ -384,6 +386,54 @@ get_class_def() const {
 }
 #endif  // HAVE_PYTHON
 
+#ifdef HAVE_PYTHON
+////////////////////////////////////////////////////////////////////
+//     Function: DCClass::has_owner_class_def
+//       Access: Published
+//  Description: Returns true if the DCClass object has an associated
+//               Python owner class definition, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool DCClass::
+has_owner_class_def() const {
+  return (_owner_class_def != NULL);
+}
+#endif  // HAVE_PYTHON
+
+#ifdef HAVE_PYTHON
+////////////////////////////////////////////////////////////////////
+//     Function: DCClass::set_owner_class_def
+//       Access: Published
+//  Description: Sets the owner class object associated with this
+//               DistributedClass.  This object will be used to
+//               construct new owner instances of the class.
+////////////////////////////////////////////////////////////////////
+void DCClass::
+set_owner_class_def(PyObject *owner_class_def) {
+  Py_XINCREF(owner_class_def);
+  Py_XDECREF(_owner_class_def);
+  _owner_class_def = owner_class_def;
+}
+#endif  // HAVE_PYTHON
+
+#ifdef HAVE_PYTHON
+////////////////////////////////////////////////////////////////////
+//     Function: DCClass::get_owner_class_def
+//       Access: Published
+//  Description: Returns the owner class object that was previously
+//               associated with this DistributedClass.  This will
+//               return a new reference to the object.
+////////////////////////////////////////////////////////////////////
+PyObject *DCClass::
+get_owner_class_def() const {
+  if (_owner_class_def == NULL) {
+    return Py_None;
+  }
+
+  Py_INCREF(_owner_class_def);
+  return _owner_class_def;
+}
+#endif  // HAVE_PYTHON
+
 #ifdef HAVE_PYTHON
 ////////////////////////////////////////////////////////////////////
 //     Function: DCClass::receive_update
@@ -453,6 +503,48 @@ receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const
 }
 #endif  // HAVE_PYTHON
 
+#ifdef HAVE_PYTHON
+////////////////////////////////////////////////////////////////////
+//     Function: DCClass::receive_update_broadcast_required_owner
+//       Access: Published
+//  Description: Processes a big datagram that includes all of the
+//               "required" fields that are sent along with a normal
+//               "generate with required" message.  This is all of the
+//               atomic fields that are marked "broadcast ownrecv". Should
+//               be used for 'owner-view' objects.
+////////////////////////////////////////////////////////////////////
+void DCClass::
+receive_update_broadcast_required_owner(PyObject *distobj,
+					DatagramIterator &di) const {
+#ifdef WITHIN_PANDA
+  PStatTimer timer(((DCClass *)this)->_class_update_pcollector);
+#endif
+  DCPacker packer;
+  packer.set_unpack_data(di.get_remaining_bytes());
+
+  int num_fields = get_num_inherited_fields();
+  for (int i = 0; i < num_fields && !PyErr_Occurred(); ++i) {
+    DCField *field = get_inherited_field(i);
+    if (field->as_molecular_field() == (DCMolecularField *)NULL &&
+        field->is_required()) {
+      packer.begin_unpack(field);
+      if (field->is_ownrecv()) {
+	field->receive_update(packer, distobj);
+      } else {
+	// It's not an ownrecv field; skip over it. It's difficult
+	// to filter this on the server, ask Roger for the reason.
+	packer.unpack_skip();
+      }
+      if (!packer.end_unpack()) {
+	break;
+      }
+    }
+  }
+
+  di.skip_bytes(packer.get_num_unpacked_bytes());
+}
+#endif  // HAVE_PYTHON
+
 #ifdef HAVE_PYTHON
 ////////////////////////////////////////////////////////////////////
 //     Function: DCClass::receive_update_all_required
@@ -939,7 +1031,7 @@ ai_format_generate(PyObject *distobj, int do_id,
 #endif  // HAVE_PYTHON
 #ifdef HAVE_PYTHON
 ////////////////////////////////////////////////////////////////////
-//     Function: DCClass::ai_database_generate
+//     Function: DCClass::ai_database_generate_context
 //       Access: Published
 //  Description: Generates a datagram containing the message necessary
 //               to create a new database distributed object from the AI.
@@ -949,6 +1041,50 @@ ai_format_generate(PyObject *distobj, int do_id,
 ////////////////////////////////////////////////////////////////////
 Datagram DCClass::
 ai_database_generate_context(
+    unsigned int context_id, unsigned int parent_id, unsigned int zone_id,
+    CHANNEL_TYPE owner_channel,
+    CHANNEL_TYPE database_server_id, CHANNEL_TYPE from_channel_id) const 
+{
+  DCPacker packer;
+  packer.raw_pack_uint8(1);
+  packer.RAW_PACK_CHANNEL(database_server_id);
+  packer.RAW_PACK_CHANNEL(from_channel_id);
+  //packer.raw_pack_uint8('A');
+  packer.raw_pack_uint16(STATESERVER_OBJECT_CREATE_WITH_REQUIRED_CONTEXT);
+  packer.raw_pack_uint32(parent_id);  
+  packer.raw_pack_uint32(zone_id);
+  packer.RAW_PACK_CHANNEL(owner_channel);
+  packer.raw_pack_uint16(_number); // DCD class ID
+  packer.raw_pack_uint32(context_id);
+
+  // Specify all of the required fields.
+  int num_fields = get_num_inherited_fields();
+  for (int i = 0; i < num_fields; ++i) {
+    DCField *field = get_inherited_field(i);
+    if (field->is_required() && field->as_molecular_field() == NULL) {
+      packer.begin_pack(field);
+      packer.pack_default_value();
+      packer.end_pack();
+    }
+  }
+
+  return Datagram(packer.get_data(), packer.get_length());
+}
+#endif  // HAVE_PYTHON
+
+#ifdef HAVE_PYTHON
+// TODO: remove this once Skyler has things working with the new server
+////////////////////////////////////////////////////////////////////
+//     Function: DCClass::ai_database_generate_context_old
+//       Access: Published
+//  Description: Generates a datagram containing the message necessary
+//               to create a new database distributed object from the AI.
+//
+//               First Pass is to only incldue required values
+//               (with Defaults).                   
+////////////////////////////////////////////////////////////////////
+Datagram DCClass::
+ai_database_generate_context_old(
     unsigned int context_id, unsigned int parent_id, unsigned int zone_id,
     CHANNEL_TYPE database_server_id, CHANNEL_TYPE from_channel_id) const 
 {

+ 8 - 1
direct/src/dcparser/dcClass.h

@@ -85,9 +85,13 @@ PUBLISHED:
   bool has_class_def() const;
   void set_class_def(PyObject *class_def);
   PyObject *get_class_def() const;
+  bool has_owner_class_def() const;
+  void set_owner_class_def(PyObject *owner_class_def);
+  PyObject *get_owner_class_def() const;
 
   void receive_update(PyObject *distobj, DatagramIterator &di) const;
   void receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const;
+  void receive_update_broadcast_required_owner(PyObject *distobj, DatagramIterator &di) const;
   void receive_update_all_required(PyObject *distobj, DatagramIterator &di) const;
   void receive_update_other(PyObject *distobj, DatagramIterator &di) const;
 
@@ -112,7 +116,9 @@ PUBLISHED:
   Datagram client_format_generate(PyObject *distobj, int do_id, int zone_id,                              
                                   PyObject *optional_fields) const;
 
-  Datagram ai_database_generate_context(unsigned int context_id, unsigned int parent_id, unsigned int zone_id,
+  Datagram ai_database_generate_context(unsigned int context_id, unsigned int parent_id, unsigned int zone_id, CHANNEL_TYPE owner_channel,
+                                CHANNEL_TYPE database_server_id, CHANNEL_TYPE from_channel_id) const;
+  Datagram ai_database_generate_context_old(unsigned int context_id, unsigned int parent_id, unsigned int zone_id,
                                 CHANNEL_TYPE database_server_id, CHANNEL_TYPE from_channel_id) const;
   
 #endif 
@@ -159,6 +165,7 @@ private:
 
 #ifdef HAVE_PYTHON
   PyObject *_class_def;
+  PyObject *_owner_class_def;
 #endif
 
   friend class DCField;

+ 11 - 11
direct/src/dcparser/dcField.cxx

@@ -300,17 +300,6 @@ is_broadcast() const {
   return has_keyword("broadcast");
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: DCField::is_p2p
-//       Access: Published
-//  Description: Returns true if the "p2p" flag is set for this
-//               field, false otherwise.
-////////////////////////////////////////////////////////////////////
-bool DCField::
-is_p2p() const {
-  return has_keyword("p2p");
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: DCField::is_ram
 //       Access: Published
@@ -366,6 +355,17 @@ is_ownsend() const {
   return has_keyword("ownsend");
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: DCField::is_ownrecv
+//       Access: Published
+//  Description: Returns true if the "ownrecv" flag is set for this
+//               field, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool DCField::
+is_ownrecv() const {
+  return has_keyword("ownrecv");
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: DCField::is_airecv
 //       Access: Published

+ 1 - 1
direct/src/dcparser/dcField.h

@@ -69,12 +69,12 @@ PUBLISHED:
 
   bool is_required() const;
   bool is_broadcast() const;
-  bool is_p2p() const;
   bool is_ram() const;
   bool is_db() const;
   bool is_clsend() const;
   bool is_clrecv() const;
   bool is_ownsend() const;
+  bool is_ownrecv() const;
   bool is_airecv() const;
 
   void output(ostream &out) const;

+ 1 - 1
direct/src/dcparser/dcFile.cxx

@@ -709,7 +709,7 @@ setup_default_keywords() {
   static KeywordDef default_keywords[] = {
     { "required", 0x0001 },
     { "broadcast", 0x0002 },
-    { "p2p", 0x0004 },
+    { "ownrecv", 0x0004 },
     { "ram", 0x0008 },
     { "db", 0x0010 },
     { "clsend", 0x0020 },

+ 5 - 2
direct/src/directnotify/Notifier.py

@@ -50,8 +50,11 @@ class Notifier:
         Notifier.serverDelta = delta + time.timezone - timezone
 
         from pandac.PandaModules import NotifyCategory
-        NotifyCategory.setServerDelta(self.serverDelta)
-            
+
+        # HACK - JAY, crashes when try to log into someone else's server
+        # NotifyCategory.setServerDelta(self.serverDelta)
+        NotifyCategory.setServerDelta(0)
+         
         self.info("Notify clock adjusted by %s (and timezone adjusted by %s hours) to synchronize with server." % (PythonUtil.formatElapsedSeconds(delta), (time.timezone - timezone) / 3600))
 
     def getTime(self):

+ 59 - 11
direct/src/directtools/DirectCameraControl.py

@@ -32,8 +32,12 @@ class DirectCameraControl(PandaObject):
         self.camManipRef = direct.group.attachNewNode('camManipRef')
         t = CAM_MOVE_DURATION
         self.actionEvents = [
+            ['DIRECT-mouse1', self.mouseRotateStart],
+            ['DIRECT-mouse1Up', self.mouseFlyStop],
             ['DIRECT-mouse2', self.mouseFlyStart],
             ['DIRECT-mouse2Up', self.mouseFlyStop],
+            ['DIRECT-mouse3', self.mouseDollyStart],
+            ['DIRECT-mouse3Up', self.mouseFlyStop],
             ]
         self.keyEvents = [
             ['c', self.centerCamIn, 0.5],
@@ -62,6 +66,8 @@ class DirectCameraControl(PandaObject):
             ]
         # set this to true to prevent the camera from rolling
         self.lockRoll = False
+        # NIK - flag to determine whether to use maya camera controls
+        self.useMayaCamControls = 0
 
     def toggleMarkerVis(self):
         if direct.cameraControl.coaMarker.isHidden():
@@ -69,28 +75,52 @@ class DirectCameraControl(PandaObject):
         else:
             direct.cameraControl.coaMarker.hide()
 
+    def mouseRotateStart(self, modifiers):
+        if self.useMayaCamControls and modifiers == 4: # alt is pressed - use maya controls
+            direct.pushUndo([direct.camera])
+            self.spawnMouseRotateTask()
+
+    def mouseDollyStart(self, modifiers):
+        if self.useMayaCamControls and modifiers == 4: # alt is pressed - use maya controls
+            # Hide the marker for this kind of motion
+            self.coaMarker.hide()
+            # Record time of start of mouse interaction
+            self.startT= globalClock.getFrameTime()
+            self.startF = globalClock.getFrameCount()
+            # Start manipulation
+            self.spawnHPanYZoom()
+
     def mouseFlyStart(self, modifiers):
         # Record undo point
         direct.pushUndo([direct.camera])
-        # Where are we in the display region?
-        if ((abs(direct.dr.mouseX) < 0.9) and (abs(direct.dr.mouseY) < 0.9)):
-            # MOUSE IS IN CENTRAL REGION
+        if self.useMayaCamControls and modifiers == 4: # alt is down, use maya controls
             # Hide the marker for this kind of motion
             self.coaMarker.hide()
             # Record time of start of mouse interaction
             self.startT= globalClock.getFrameTime()
             self.startF = globalClock.getFrameCount()
             # Start manipulation
-            self.spawnXZTranslateOrHPanYZoom()
-            # END MOUSE IN CENTRAL REGION
+            self.spawnXZTranslate()
         else:
-            if ((abs(direct.dr.mouseX) > 0.9) and
-                (abs(direct.dr.mouseY) > 0.9)):
-                # Mouse is in corners, spawn roll task
-                self.spawnMouseRollTask()
+            # Where are we in the display region?
+            if ((abs(direct.dr.mouseX) < 0.9) and (abs(direct.dr.mouseY) < 0.9)):
+                # MOUSE IS IN CENTRAL REGION
+                # Hide the marker for this kind of motion
+                self.coaMarker.hide()
+                # Record time of start of mouse interaction
+                self.startT= globalClock.getFrameTime()
+                self.startF = globalClock.getFrameCount()
+                # Start manipulation
+                self.spawnXZTranslateOrHPanYZoom()
+                # END MOUSE IN CENTRAL REGION
             else:
-                # Mouse is in outer frame, spawn mouseRotateTask
-                self.spawnMouseRotateTask()
+                if ((abs(direct.dr.mouseX) > 0.9) and
+                    (abs(direct.dr.mouseY) > 0.9)):
+                    # Mouse is in corners, spawn roll task
+                    self.spawnMouseRollTask()
+                else:
+                    # Mouse is in outer frame, spawn mouseRotateTask
+                    self.spawnMouseRotateTask()
 
     def mouseFlyStop(self):
         taskMgr.remove('manipulateCamera')
@@ -119,6 +149,15 @@ class DirectCameraControl(PandaObject):
         # Resize it
         self.updateCoaMarkerSize()
 
+    def mouseFlyStartTopWin(self):
+        print "Moving mouse 2 in new window"
+        #altIsDown = base.getAlt()
+        #if altIsDown:
+        #    print "Alt is down"
+
+    def mouseFlyStopTopWin(self):
+        print "Stopping mouse 2 in new window"
+
     def spawnXZTranslateOrHPanYZoom(self):
         # Kill any existing tasks
         taskMgr.remove('manipulateCamera')
@@ -183,6 +222,11 @@ class DirectCameraControl(PandaObject):
         return Task.cont
 
     def HPanYZoomTask(self,state):
+        # If the cam is orthogonal, don't rotate or zoom.
+        if (hasattr(direct.camera.node(), "getLens") and
+            direct.camera.node().getLens().__class__.__name__ == "OrthographicLens"):
+            return
+        
         if direct.fControl:
             moveDir = Vec3(self.coaMarker.getPos(direct.camera))
             # If marker is behind camera invert vector
@@ -230,6 +274,10 @@ class DirectCameraControl(PandaObject):
         taskMgr.add(t, 'manipulateCamera')
 
     def mouseRotateTask(self, state):
+        # If the cam is orthogonal, don't rotate.
+        if (hasattr(direct.camera.node(), "getLens") and
+            direct.camera.node().getLens().__class__.__name__ == "OrthographicLens"):
+            return
         # If moving outside of center, ignore motion perpendicular to edge
         if ((state.constrainedDir == 'y') and (abs(direct.dr.mouseX) > 0.9)):
             deltaX = 0

+ 13 - 11
direct/src/directtools/DirectManipulation.py

@@ -54,17 +54,19 @@ class DirectManipulationControl(PandaObject):
         else:
             # Nope, off the widget, no constraint
             self.constraint = None
-        # Check to see if we are moving the object
-        # We are moving the object if we either wait long enough
-        taskMgr.doMethodLater(MANIPULATION_MOVE_DELAY,
-                              self.switchToMoveMode,
-                              'manip-move-wait')
-        # Or we move far enough
-        self.moveDir = None
-        watchMouseTask = Task.Task(self.watchMouseTask)
-        watchMouseTask.initX = direct.dr.mouseX
-        watchMouseTask.initY = direct.dr.mouseY
-        taskMgr.add(watchMouseTask, 'manip-watch-mouse')
+            
+        if not direct.gotAlt(modifiers):
+            # Check to see if we are moving the object
+            # We are moving the object if we either wait long enough
+            taskMgr.doMethodLater(MANIPULATION_MOVE_DELAY,
+                                  self.switchToMoveMode,
+                                  'manip-move-wait')
+            # Or we move far enough
+            self.moveDir = None
+            watchMouseTask = Task.Task(self.watchMouseTask)
+            watchMouseTask.initX = direct.dr.mouseX
+            watchMouseTask.initY = direct.dr.mouseY
+            taskMgr.add(watchMouseTask, 'manip-watch-mouse')
 
     def switchToMoveMode(self, state):
         taskMgr.remove('manip-watch-mouse')

+ 36 - 28
direct/src/distributed/AsyncRequest.py

@@ -42,6 +42,8 @@ class AsyncRequest(DirectObject):
         timeout is how many seconds to wait before aborting the request.
         """
         assert self.notify.debugCall()
+        if __debug__:
+            self.__deleted=False
         assert isinstance(air, ConnectionRepository) # The api to AsyncRequest has changed.
         #DirectObject.DirectObject.__init__(self)
         self.air=air
@@ -52,9 +54,12 @@ class AsyncRequest(DirectObject):
 
     def delete(self):
         assert self.notify.debugCall()
+        assert not self.__deleted
+        if __debug__:
+            self.__deleted=True
+        self.ignoreAll()
         self.timeoutTask.remove()
         del self.timeoutTask
-        self.ignoreAll()
         if 0:
             for i in self.neededObjects.values():
                 if i is not None:
@@ -77,11 +82,13 @@ class AsyncRequest(DirectObject):
         call this base method to cleanup.
         """
         assert self.notify.debugCall("neededObjects: %s"%(self.neededObjects,))
+        assert not self.__deleted
         if __debug__:
             global BreakOnTimeout
             if BreakOnTimeout:
-                print "\n\nself.neededObjects =", self.neededObjects
-                print "\ntimed out after %s seconds."%(task.delayTime,)
+                print "\n\nself.avatarId =", self.avatarId
+                print "\nself.neededObjects =", self.neededObjects
+                print "\ntimed out after %s seconds.\n\n"%(task.delayTime,)
                 import pdb; pdb.set_trace()
         self.delete()
 
@@ -91,6 +98,7 @@ class AsyncRequest(DirectObject):
         finish() if we do.
         """
         assert self.notify.debugCall()
+        assert not self.__deleted
         if name is not None:
             self.neededObjects[name]=distObj
         else:
@@ -106,6 +114,7 @@ class AsyncRequest(DirectObject):
         Request an already created object, i.e. read from database.
         """
         assert self.notify.debugCall()
+        assert not self.__deleted
         if key is None:
             # default the dictionary key to the fieldName
             key = fieldName
@@ -128,12 +137,14 @@ class AsyncRequest(DirectObject):
         Request an already created object, i.e. read from database.
         """
         assert self.notify.debugCall()
+        assert not self.__deleted
         assert doId
-        object = self.air.doId2do.get(doId)
-        self.neededObjects[doId]=object
-        if object is not None:
-            self._checkCompletion(None, context, object)
-        else:
+        #object = self.air.doId2do.get(doId)
+        #self.neededObjects[doId]=object
+        #if object is not None:
+        #    self._checkCompletion(None, context, object)
+        #else:
+        if 1:
             if context is None:
                 context=self.air.allocateContext()
             self.acceptOnce(
@@ -141,24 +152,6 @@ class AsyncRequest(DirectObject):
                 self._checkCompletion, [None])
             self.air.queryObjectAll(doId, context)
 
-    ## def askForObjectAIReceive(self, doId, context=None):
-        ## """
-        ## Request an already created object, i.e. read from database.
-        ## """
-        ## assert self.notify.debugCall()
-        ## assert doId
-        ## object = self.air.doId2do.get(doId)
-        ## self.neededObjects[doId]=object
-        ## if object is not None:
-            ## self._checkCompletion(None, context, object)
-        ## else:
-            ## if context is None:
-                ## context=self.air.allocateContext()
-            ## self.accept(
-                ## "doRequestResponse-%s"%(context,), 
-                ## self._checkCompletion, [None])
-            ## self.air.queryObjectAIReceive(doId, context)
-
     #def addInterestInObject(self, doId, context=None):
     #    """
     #    Request an already created object, i.e. read from database
@@ -184,14 +177,28 @@ class AsyncRequest(DirectObject):
         your self.finish() function.
         """
         assert self.notify.debugCall()
+        assert not self.__deleted
         assert name
         assert className
         self.neededObjects[name]=None
         if context is None:
             context=self.air.allocateContext()
-        self.accept(
-            "doRequestResponse-%s"%(context,), self._checkCompletion, [name])
+        newDBRequestGen = config.GetBool( #HACK:
+            'new-database-request-generate', 1)
+        if newDBRequestGen:
+            self.accept(
+                self.air.getDatabaseGenerateResponseEvent(context),
+                self._doCreateObject, [name, className, values])
+        else:
+            self.accept(
+                "doRequestResponse-%s"%(context,), self._checkCompletion, [name])
         self.air.requestDatabaseGenerate(className, context, values=values)
+    
+    def _doCreateObject(self, name, className, values, doId):
+        assert self.notify.debugCall()
+        assert not self.__deleted
+        distObj = self.air.generateGlobalObject(doId, className, values)
+        self._checkCompletion(name, None, distObj)
 
     def finish(self):
         """
@@ -201,4 +208,5 @@ class AsyncRequest(DirectObject):
         If the other requests timeout, finish will not be called.
         """
         assert self.notify.debugCall()
+        assert not self.__deleted
         self.delete()

+ 0 - 2
direct/src/distributed/CRCache.py

@@ -10,7 +10,6 @@ class CRCache:
         self.maxCacheItems = maxCacheItems
         self.dict = {}
         self.fifo = []
-        return None
 
     def flush(self):
         """
@@ -52,7 +51,6 @@ class CRCache:
                 
         # Make sure that the fifo and the dictionary are sane
         assert(len(self.dict) == len(self.fifo))
-        return None
 
     def retrieve(self, doId):
         assert(self.checkCache())

+ 132 - 44
direct/src/distributed/ClientRepository.py

@@ -25,7 +25,8 @@ class ClientRepository(ConnectionRepository):
     notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository")
 
     def __init__(self):
-        ConnectionRepository.__init__(self, base.config)
+        self.dcSuffix=""
+        ConnectionRepository.__init__(self, base.config, hasOwnerView=True)
 
         self.context=100000
         self.setClientDatagram(1)
@@ -38,6 +39,7 @@ class ClientRepository(ConnectionRepository):
 
         self.readDCFile()
         self.cache=CRCache.CRCache()
+        self.cacheOwner=CRCache.CRCache()
         self.serverDelta = 0
 
         self.bootedIndex = None
@@ -76,10 +78,31 @@ class ClientRepository(ConnectionRepository):
         self.DOIDnext = 0
         self.DOIDlast = 0
 
+    ## def queryObjectAll(self, doID, context=0):
+        ## """
+        ## Get a one-time snapshot look at the object.
+        ## """
+        ## assert self.notify.debugStateCall(self)
+        ## # Create a message
+        ## datagram = PyDatagram()
+        ## datagram.addServerHeader(
+            ## doID, localAvatar.getDoId(), 2020)           
+        ## # A context that can be used to index the response if needed
+        ## datagram.addUint32(context)
+        ## self.send(datagram)
+        ## # Make sure the message gets there.
+        ## self.flush()
+
     # Define uniqueName
     def uniqueName(self, desc):
         return desc
 
+    def getTables(self, ownerView):
+        if ownerView:
+            return self.doId2ownerView, self.cacheOwner
+        else:
+            return self.doId2do, self.cache
+
     def abruptCleanup(self):
         """
         Call this method to clean up any pending hooks or tasks on
@@ -172,6 +195,7 @@ class ClientRepository(ConnectionRepository):
         if wantOtpServer:
             parentId = di.getUint32()
             zoneId = di.getUint32()
+            assert parentId == 4617 or parentId in self.doId2do
         # Get the class Id
         classId = di.getUint16()
         # Get the DO Id
@@ -190,6 +214,7 @@ class ClientRepository(ConnectionRepository):
         if wantOtpServer:
             parentId = di.getUint32()
             zoneId = di.getUint32()
+            assert parentId == 4617 or parentId in self.doId2do
         # Get the class Id
         classId = di.getUint16()
         # Get the DO Id
@@ -204,11 +229,28 @@ class ClientRepository(ConnectionRepository):
             distObj = self.generateWithRequiredOtherFields(dclass, doId, di)
         dclass.stopGenerate()
 
+    def handleGenerateWithRequiredOtherOwner(self, di):
+        assert wantOtpServer
+        # Get the class Id
+        classId = di.getUint16()
+        # Get the DO Id
+        doId = di.getUint32()
+        # parentId and zoneId are not relevant here
+        parentId = di.getUint32()
+        zoneId = di.getUint32()
+        # Look up the dclass
+        dclass = self.dclassesByNumber[classId]
+        dclass.startGenerate()
+        # Create a new distributed object, and put it in the dictionary
+        distObj = self.generateWithRequiredOtherFieldsOwner(dclass, doId, di)
+        dclass.stopGenerate()
+
     def handleQuietZoneGenerateWithRequired(self, di):
         # Special handler for quiet zone generates -- we need to filter
         if wantOtpServer:
             parentId = di.getUint32()
             zoneId = di.getUint32()
+            assert parentId in self.doId2do
         # Get the class Id
         classId = di.getUint16()
         # Get the DO Id
@@ -231,6 +273,7 @@ class ClientRepository(ConnectionRepository):
         if wantOtpServer:
             parentId = di.getUint32()
             zoneId = di.getUint32()
+            assert parentId in self.doId2do
         # Get the class Id
         classId = di.getUint16()
         # Get the DO Id
@@ -296,36 +339,36 @@ class ClientRepository(ConnectionRepository):
                 print "New DO:%s, dclass:%s"%(doId, dclass.getName())
         return distObj
 
-    def generateGlobalObject(self, doId, dcname):
-        # Look up the dclass
-        dclass = self.dclassesByName[dcname]
-        # Create a new distributed object, and put it in the dictionary
-        #distObj = self.generateWithRequiredFields(dclass, doId, di)
-
-        # Construct a new one
-        classDef = dclass.getClassDef()
-        if classDef == None:
-             self.notify.error("Could not create an undefined %s object."%(
-                dclass.getName()))
-        distObj = classDef(self)
-        distObj.dclass = dclass
-        # Assign it an Id
-        distObj.doId = doId
-        # Put the new do in the dictionary
-        self.doId2do[doId] = distObj
-        # Update the required fields
-        distObj.generateInit()  # Only called when constructed
-        distObj.generate()
-        if wantOtpServer:
-            # TODO: ROGER: where should we get parentId and zoneId?
-            parentId = None
-            zoneId = None
-            distObj.setLocation(parentId, zoneId)
-        # updateRequiredFields calls announceGenerate
-        return  distObj
+    ## def generateGlobalObject(self, doId, dcname):
+        ## # Look up the dclass
+        ## dclass = self.dclassesByName[dcname]
+        ## # Create a new distributed object, and put it in the dictionary
+        ## #distObj = self.generateWithRequiredFields(dclass, doId, di)
+
+        ## # Construct a new one
+        ## classDef = dclass.getClassDef()
+        ## if classDef == None:
+             ## self.notify.error("Could not create an undefined %s object."%(
+                ## dclass.getName()))
+        ## distObj = classDef(self)
+        ## distObj.dclass = dclass
+        ## # Assign it an Id
+        ## distObj.doId = doId
+        ## # Put the new do in the dictionary
+        ## self.doId2do[doId] = distObj
+        ## # Update the required fields
+        ## distObj.generateInit()  # Only called when constructed
+        ## distObj.generate()
+        ## if wantOtpServer:
+            ## # TODO: ROGER: where should we get parentId and zoneId?
+            ## parentId = None
+            ## zoneId = None
+            ## distObj.setLocation(parentId, zoneId)
+        ## # updateRequiredFields calls announceGenerate
+        ## return  distObj
 
     def generateWithRequiredOtherFields(self, dclass, doId, di,
-            parentId = None, zoneId = None):
+                                        parentId = None, zoneId = None):
         if self.doId2do.has_key(doId):
             # ...it is in our dictionary.
             # Just update it.
@@ -370,40 +413,82 @@ class ClientRepository(ConnectionRepository):
             # updateRequiredOtherFields calls announceGenerate
         return distObj
 
+    def generateWithRequiredOtherFieldsOwner(self, dclass, doId, di):
+        if self.doId2ownerView.has_key(doId):
+            # ...it is in our dictionary.
+            # Just update it.
+            distObj = self.doId2ownerView[doId]
+            assert(distObj.dclass == dclass)
+            distObj.generate()
+            distObj.updateRequiredOtherFields(dclass, di)
+            # updateRequiredOtherFields calls announceGenerate
+        elif self.cacheOwner.contains(doId):
+            # ...it is in the cache.
+            # Pull it out of the cache:
+            distObj = self.cacheOwner.retrieve(doId)
+            assert(distObj.dclass == dclass)
+            # put it in the dictionary:
+            self.doId2ownerView[doId] = distObj
+            # and update it.
+            distObj.generate()
+            distObj.updateRequiredOtherFields(dclass, di)
+            # updateRequiredOtherFields calls announceGenerate
+        else:
+            # ...it is not in the dictionary or the cache.
+            # Construct a new one
+            classDef = dclass.getOwnerClassDef()
+            if classDef == None:
+                self.notify.error("Could not create an undefined %s object. Have you created an owner view?" % (dclass.getName()))
+            distObj = classDef(self)
+            distObj.dclass = dclass
+            # Assign it an Id
+            distObj.doId = doId
+            # Put the new do in the dictionary
+            self.doId2ownerView[doId] = distObj
+            # Update the required fields
+            distObj.generateInit()  # Only called when constructed
+            distObj.generate()
+            distObj.updateRequiredOtherFields(dclass, di)
+            # updateRequiredOtherFields calls announceGenerate
+        return distObj
+
 
-    def handleDisable(self, di):
+    def handleDisable(self, di, ownerView=False):
         # Get the DO Id
         doId = di.getUint32()
         # disable it.
-        self.disableDoId(doId)
+        self.disableDoId(doId, ownerView)
 
-    def disableDoId(self, doId):
-         # Make sure the object exists
-        if self.doId2do.has_key(doId):
+    def disableDoId(self, doId, ownerView=False):
+        table, cache = self.getTables(ownerView)
+        # Make sure the object exists
+        if table.has_key(doId):
             # Look up the object
-            distObj = self.doId2do[doId]
+            distObj = table[doId]
             # remove the object from the dictionary
-            del self.doId2do[doId]
+            del table[doId]
 
             # Only cache the object if it is a "cacheable" type
             # object; this way we don't clutter up the caches with
             # trivial objects that don't benefit from caching.
             if distObj.getCacheable():
-                self.cache.cache(distObj)
+                cache.cache(distObj)
             else:
                 distObj.deleteOrDelay()
         else:
             ClientRepository.notify.warning(
                 "Disable failed. DistObj "
                 + str(doId) +
-                " is not in dictionary")
+                " is not in dictionary, ownerView=%s" % ownerView)
 
     def handleDelete(self, di):
+        if wantOtpServer:
+            assert 0
         # Get the DO Id
         doId = di.getUint32()
         self.deleteObject(doId)
 
-    def deleteObject(self, doId):
+    def deleteObject(self, doId, ownerView=False):
         """
         Removes the object from the client's view of the world.  This
         should normally not be called except in the case of error
@@ -418,6 +503,8 @@ class ClientRepository(ConnectionRepository):
         This is not a distributed message and does not delete the
         object on the server or on any other client.
         """
+        if wantOtpServer:
+            assert 0
         if self.doId2do.has_key(doId):
             # If it is in the dictionary, remove it.
             obj = self.doId2do[doId]
@@ -527,16 +614,17 @@ class ClientRepository(ConnectionRepository):
                 self.handleGenerateWithRequired(di)
             elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER:
                 self.handleGenerateWithRequiredOther(di)
+            elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER_OWNER:
+                self.handleGenerateWithRequiredOtherOwner(di)
             elif msgType == CLIENT_OBJECT_UPDATE_FIELD:
                 self.handleUpdateField(di)
-            elif msgType == CLIENT_OBJECT_DISABLE_RESP:
+            elif msgType == CLIENT_OBJECT_DISABLE:
                 self.handleDisable(di)
+            elif msgType == CLIENT_OBJECT_DISABLE_OWNER:
+                self.handleDisable(di, ownerView=True)
             elif msgType == CLIENT_OBJECT_DELETE_RESP:
+                assert 0
                 self.handleDelete(di)
-            elif msgType == CLIENT_CREATE_OBJECT_REQUIRED:
-                self.handleGenerateWithRequired(di)
-            elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER:
-                self.handleGenerateWithRequiredOther(di)
             elif msgType == CLIENT_DONE_INTEREST_RESP:
                 self.handleInterestDoneMessage(di)
             elif msgType == CLIENT_QUERY_ONE_FIELD_RESP:

+ 102 - 5
direct/src/distributed/ConnectionRepository.py

@@ -19,11 +19,17 @@ class ConnectionRepository(
     notify = DirectNotifyGlobal.directNotify.newCategory("ConnectionRepository")
     taskPriority = -30
 
-    def __init__(self, config):
+    def __init__(self, config, hasOwnerView=False):
         assert self.notify.debugCall()
+        # let the C connection repository know whether we're supporting
+        # 'owner' views of distributed objects (i.e. 'receives ownrecv',
+        # 'I own this object and have a separate view of it regardless of
+        # where it currently is located')
+        CConnectionRepository.__init__(self, hasOwnerView)
+        # DoInterestManager.__init__ relies on CConnectionRepository being
+        # initialized
         DoInterestManager.__init__(self)
         DoCollectionManager.__init__(self)
-        CConnectionRepository.__init__(self)
         self.setPythonRepository(self)
 
         self.config = config
@@ -59,9 +65,44 @@ class ConnectionRepository(
         # DC file.  The AIRepository will redefine this to 'AI'.
         self.dcSuffix = ''
 
-    def generateGlobalObject(self, doId, dcname):
+    def generateGlobalObject(self, doId, dcname, values=None):
+        def applyFieldValues(distObj, dclass, values):
+            for i in range(dclass.getNumInheritedFields()):
+                field = dclass.getInheritedField(i)
+                if field.asMolecularField() == None:
+                    value = values.get(field.getName(), None)
+                    if value is None and field.isRequired():
+                        # Gee, this could be better.  What would really be
+                        # nicer is to get value from field.getDefaultValue
+                        # or similar, but that returns a binary string, not
+                        # a python tuple, like the following does.  If you
+                        # want to change something better, please go ahead.
+                        packer = DCPacker()
+                        packer.beginPack(field)
+                        packer.packDefaultValue()
+                        packer.endPack()
+                        
+                        unpacker = DCPacker()
+                        unpacker.setUnpackData(packer.getString())
+                        unpacker.beginUnpack(field)
+                        value = unpacker.unpackObject()
+                        unpacker.endUnpack()
+                    if value is not None:
+                        try:
+                            function = getattr(distObj, field.getName())
+                            function(*value)
+                        except AttributeError:
+                            self.notify.error("\n\n\nNot able to find %s.%s"%(
+                                distObj.__class__.__name__, field.getName()))
+                            pass
+            
         # Look up the dclass
-        dclass = self.dclassesByName[dcname+self.dcSuffix]
+        dclass = self.dclassesByName.get(dcname+self.dcSuffix)
+        if dclass is None:
+            print "\n\n\nNeed to define", dcname+self.dcSuffix
+            dclass = self.dclassesByName.get(dcname+'AI')
+        if dclass is None:
+            dclass = self.dclassesByName.get(dcname)
         # Create a new distributed object, and put it in the dictionary
         #distObj = self.generateWithRequiredFields(dclass, doId, di)
 
@@ -79,6 +120,8 @@ class ConnectionRepository(
         # Update the required fields
         distObj.generateInit()  # Only called when constructed
         distObj.generate()
+        if values is not None:
+            applyFieldValues(distObj, dclass, values)
         distObj.announceGenerate()
         distObj.parentId = 0
         distObj.zoneId = 0
@@ -121,7 +164,7 @@ class ConnectionRepository(
 
         # Now import all of the modules required by the DC file.
         for n in range(dcFile.getNumImportModules()):
-            moduleName = dcFile.getImportModule(n)
+            moduleName = dcFile.getImportModule(n)[:]
 
             # Maybe the module name is represented as "moduleName/AI".
             suffix = moduleName.split('/')
@@ -184,10 +227,64 @@ class ConnectionRepository(
                 #else:
                 #    dclass.setClassDef(classDef)
                 dclass.setClassDef(classDef)
+
             self.dclassesByName[className] = dclass
             if number >= 0:
                 self.dclassesByNumber[number] = dclass
 
+        # Owner Views
+        if self.hasOwnerView():
+            ownerDcSuffix = self.dcSuffix + 'OV'
+            # dict of class names (without 'OV') that have owner views
+            ownerImportSymbols = {}
+
+            # Now import all of the modules required by the DC file.
+            for n in range(dcFile.getNumImportModules()):
+                moduleName = dcFile.getImportModule(n)
+
+                # Maybe the module name is represented as "moduleName/AI".
+                suffix = moduleName.split('/')
+                moduleName = suffix[0]
+                suffix=suffix[1:]
+                if ownerDcSuffix in suffix:
+                    moduleName = moduleName + ownerDcSuffix
+
+                importSymbols = []
+                for i in range(dcFile.getNumImportSymbols(n)):
+                    symbolName = dcFile.getImportSymbol(n, i)
+
+                    # Check for the OV suffix
+                    suffix = symbolName.split('/')
+                    symbolName = suffix[0]
+                    suffix=suffix[1:]
+                    if ownerDcSuffix in suffix:
+                        symbolName += ownerDcSuffix
+                    importSymbols.append(symbolName)
+                    ownerImportSymbols[symbolName] = None
+
+                self.importModule(dcImports, moduleName, importSymbols)
+
+            # Now get the class definition for the owner classes named
+            # in the DC file.
+            for i in range(dcFile.getNumClasses()):
+                dclass = dcFile.getClass(i)
+                if ((dclass.getName()+ownerDcSuffix) in ownerImportSymbols):
+                    number = dclass.getNumber()
+                    className = dclass.getName() + ownerDcSuffix
+
+                    # Does the class have a definition defined in the newly
+                    # imported namespace?
+                    classDef = dcImports.get(className)
+                    if classDef is None:
+                        self.notify.error("No class definition for %s." % className)
+                    else:
+                        if type(classDef) == types.ModuleType:
+                            if not hasattr(classDef, className):
+                                self.notify.error("Module %s does not define class %s." % (className, className))
+                            classDef = getattr(classDef, className)
+                        dclass.setOwnerClassDef(classDef)
+                        self.dclassesByName[className] = dclass
+
     def importModule(self, dcImports, moduleName, importSymbols):
         """
         Imports the indicated moduleName and all of its symbols

+ 0 - 1
direct/src/distributed/DistributedCartesianGrid.py

@@ -160,7 +160,6 @@ class DistributedCartesianGrid(DistributedNode.DistributedNode,
                 
         # Set the location on the server
         av.b_setLocation(self.doId, zoneId)
-        return
 
 
     ##################################################

+ 94 - 0
direct/src/distributed/DistributedNodeUD.py

@@ -0,0 +1,94 @@
+from otp.ai.AIBaseGlobal import *
+from DistributedObjectUD import DistributedObjectUD
+
+class DistributedNodeUD(DistributedObjectUD):
+    def __init__(self, air, name=None):
+        # Be careful not to create multiple NodePath objects
+        try:
+            self.DistributedNodeUD_initialized
+        except:
+            self.DistributedNodeUD_initialized = 1
+            DistributedObjectUD.__init__(self, air)
+            if name is None:
+                name = self.__class__.__name__
+
+    def b_setParent(self, parentToken):
+        if type(parentToken) == types.StringType:
+            self.setParentStr(parentToken)
+        else:
+            self.setParent(parentToken)
+        self.d_setParent(parentToken)
+
+    def d_setParent(self, parentToken):
+        if type(parentToken) == type(''):
+            self.sendUpdate("setParentStr", [parentToken])
+        else:
+            self.sendUpdate("setParent", [parentToken])
+
+    def setParentStr(self, parentToken):
+        self.notify.debugCall()
+        self.do_setParent(parentToken)
+
+    def setParent(self, parentToken):
+        self.notify.debugCall()
+        self.do_setParent(parentToken)
+
+    def do_setParent(self, parentToken):
+        self.getParentMgr().requestReparent(self, parentToken)
+
+    ###### set pos and hpr functions #######
+
+    # setX provided by NodePath
+    def d_setX(self, x):
+        self.sendUpdate("setX", [x])
+
+    # setY provided by NodePath
+    def d_setY(self, y):
+        self.sendUpdate("setY", [y])
+    
+    # setZ provided by NodePath
+    def d_setZ(self, z):
+        self.sendUpdate("setZ", [z])
+    
+    # setH provided by NodePath
+    def d_setH(self, h):
+        self.sendUpdate("setH", [h])
+    
+    # setP provided by NodePath
+    def d_setP(self, p):
+        self.sendUpdate("setP", [p])
+    
+    # setR provided by NodePath
+    def d_setR(self, r):
+        self.sendUpdate("setR", [r])
+
+    def setXY(self, x, y):
+        self.setX(x)
+        self.setY(y)
+    def d_setXY(self, x, y):
+        self.sendUpdate("setXY", [x, y])
+
+    # setPos provided by NodePath
+    def d_setPos(self, x, y, z):
+        self.sendUpdate("setPos", [x, y, z])
+
+    # setHpr provided by NodePath
+    def d_setHpr(self, h, p, r):
+        self.sendUpdate("setHpr", [h, p, r])
+
+    def setXYH(self, x, y, h):
+        self.setX(x)
+        self.setY(y)
+        self.setH(h)
+    def d_setXYH(self, x, y, h):
+        self.sendUpdate("setXYH", [x, y, h])
+
+    def setXYZH(self, x, y, z, h):
+        self.setPos(x, y, z)
+        self.setH(h)
+    def d_setXYZH(self, x, y, z, h):
+        self.sendUpdate("setXYZH", [x, y, z, h])
+
+    # setPosHpr provided by NodePath
+    def d_setPosHpr(self, x, y, z, h, p, r):
+        self.sendUpdate("setPosHpr", [x, y, z, h, p, r])

+ 4 - 1
direct/src/distributed/DistributedObject.py

@@ -108,7 +108,6 @@ class DistributedObject(PandaObject):
     def setCacheable(self, bool):
         assert((bool == 1) or (bool == 0))
         self.cacheable = bool
-        return None
 
     def getCacheable(self):
         return self.cacheable
@@ -384,6 +383,10 @@ class DistributedObject(PandaObject):
             self.cr.sendSetLocation(self.doId, parentId, zoneId)
             
         def setLocation(self, parentId, zoneId):
+            # Prevent Duplicate SetLocations for being Called
+            if (self.parentId == parentId) and (self.zoneId == zoneId):
+                return
+
             #self.notify.info("setLocation: %s parentId: %s zoneId: %s" % (self.doId, parentId, zoneId))
             # parentId can be 'None', e.g. when an object is being disabled
             oldParentId = self.parentId

+ 4 - 0
direct/src/distributed/DistributedObjectAI.py

@@ -182,6 +182,10 @@ class DistributedObjectAI(DirectObject):
             self.air.sendSetLocation(self, parentId, zoneId)
 
         def setLocation(self, parentId, zoneId):
+            # Prevent Duplicate SetLocations for being Called
+            if (self.parentId == parentId) and (self.zoneId == zoneId):
+                return
+
             oldParentId = self.parentId
             oldZoneId = self.zoneId
             if ((oldParentId != parentId) or

+ 27 - 0
direct/src/distributed/DistributedObjectGlobalUD.py

@@ -0,0 +1,27 @@
+
+
+from DistributedObjectUD import DistributedObjectUD
+from direct.directnotify.DirectNotifyGlobal import directNotify
+
+if __debug__:
+    notify = directNotify.newCategory('DistributedObjectGlobalUD')
+
+
+class DistributedObjectGlobalUD(DistributedObjectUD):
+    if __debug__:
+        notify = notify
+
+    doNotDeallocateChannel = 1
+    isGlobalDistObj = 1
+
+    def __init__(self, air):
+        DistributedObjectUD.__init__(self, air)
+    
+    def announceGenerate(self):
+        self.air.registerForChannel(self.doId)
+        DistributedObjectUD.announceGenerate(self)
+    
+    def delete(self):
+        self.air.unregisterForChannel(self.doId)
+        ## self.air.removeDOFromTables(self)
+        DistributedObjectUD.delete(self)

+ 216 - 0
direct/src/distributed/DistributedObjectOV.py

@@ -0,0 +1,216 @@
+from direct.showbase.PandaObject import *
+from direct.directnotify.DirectNotifyGlobal import directNotify
+
+from PyDatagram import PyDatagram
+from PyDatagramIterator import PyDatagramIterator
+
+# Values for DistributedObjectOV.activeState
+# these should match DistributedObject.ES*
+
+ESNew          = 1
+ESDeleted      = 2
+ESDisabling    = 3
+ESDisabled     = 4  # values here and lower are considered "disabled"
+ESGenerating   = 5  # values here and greater are considered "generated"
+ESGenerated    = 6
+
+class DistributedObjectOV(PandaObject):
+    """
+    Implementation of the 'owner view' (OV) of a distributed object; 
+    """
+    notify = directNotify.newCategory("DistributedObjectOV")
+
+    def __init__(self, cr):
+        assert self.notify.debugStateCall(self)
+        try:
+            self.DistributedObjectOV_initialized
+        except:
+            self.DistributedObjectOV_initialized = 1
+            self.cr = cr
+
+            # This count tells whether the object can be deleted right away,
+            # or not.
+            self.delayDeleteCount = 0
+            # This flag tells whether a delete has been requested on this
+            # object.
+            self.deleteImminent = 0
+
+            # Keep track of our state as a distributed object.  This
+            # is only trustworthy if the inheriting class properly
+            # calls up the chain for disable() and generate().
+            self.activeState = ESNew
+
+    if __debug__:
+        def status(self, indent=0):
+            """
+            print out "doId(parentId,zoneId) className"
+                and conditionally show generated, disabled
+            """
+            spaces=' '*(indent+2)
+            try:
+                print "%s%s:"%(
+                    ' '*indent, self.__class__.__name__)
+                print "%sfrom DistributedObjectOV doId:%s, parent:%s, zone:%s"%(
+                    spaces, 
+                    self.doId, self.parentId, self.zoneId),
+                flags=[]
+                if self.activeState == ESGenerated:
+                    flags.append("generated")
+                if self.activeState < ESGenerating:
+                    flags.append("disabled")
+                if len(flags):
+                    print "(%s)"%(" ".join(flags),),
+                print
+            except Exception, e: print "%serror printing status"%(spaces,), e
+
+    def deleteOrDelay(self):
+        if self.delayDeleteCount > 0:
+            self.deleteImminent = 1
+        else:
+            self.disableAnnounceAndDelete()
+
+    def delayDelete(self, flag):
+        # Flag should be 0 or 1, meaning increment or decrement count
+        # Also see DelayDelete.py
+
+        if (flag == 1):
+            self.delayDeleteCount += 1
+        elif (flag == 0):
+            self.delayDeleteCount -= 1
+        else:
+            self.notify.error("Invalid flag passed to delayDelete: " + str(flag))
+
+        if (self.delayDeleteCount < 0):
+            self.notify.error("Somebody decremented delayDelete for doId %s without incrementing"
+                              % (self.doId))
+        elif (self.delayDeleteCount == 0):
+            assert(self.notify.debug("delayDeleteCount for doId %s now 0"
+                                     % (self.doId)))
+            if self.deleteImminent:
+                assert(self.notify.debug("delayDeleteCount for doId %s -- deleteImminent"
+                                         % (self.doId)))
+                self.disableAnnounceAndDelete()
+        else:
+            self.notify.debug("delayDeleteCount for doId %s now %s"
+                              % (self.doId, self.delayDeleteCount))
+
+        # Return the count just for kicks
+        return self.delayDeleteCount
+
+    def disableAnnounceAndDelete(self):
+        self.disableAndAnnounce()
+        self.delete()
+
+    def disableAndAnnounce(self):
+        """
+        Inheritors should *not* redefine this function.
+        """
+        # We must send the disable announce message *before* we
+        # actually disable the object.  That way, the various cleanup
+        # tasks can run first and take care of restoring the object to
+        # a normal, nondisabled state; and *then* the disable function
+        # can properly disable it (for instance, by parenting it to
+        # hidden).
+        if self.activeState != ESDisabled:
+            self.activeState = ESDisabling
+            messenger.send(self.uniqueName("disable"))
+            self.disable()
+
+    def announceGenerate(self):
+        """
+        Sends a message to the world after the object has been
+        generated and all of its required fields filled in.
+        """
+        assert(self.notify.debug('announceGenerate(): %s' % (self.doId)))
+        if self.activeState != ESGenerated:
+            self.activeState = ESGenerated
+            messenger.send(self.uniqueName("generate"), [self])
+
+    def disable(self):
+        """
+        Inheritors should redefine this to take appropriate action on disable
+        """
+        assert(self.notify.debug('disable(): %s' % (self.doId)))
+        if self.activeState != ESDisabled:
+            self.activeState = ESDisabled
+
+    def isDisabled(self):
+        """
+        Returns true if the object has been disabled and/or deleted,
+        or if it is brand new and hasn't yet been generated.
+        """
+        return (self.activeState < ESGenerating)
+
+    def isGenerated(self):
+        """
+        Returns true if the object has been fully generated by now,
+        and not yet disabled.
+        """
+        assert self.notify.debugStateCall(self)
+        return (self.activeState == ESGenerated)
+
+    def delete(self):
+        """
+        Inheritors should redefine this to take appropriate action on delete
+        """
+        assert(self.notify.debug('delete(): %s' % (self.doId)))
+        try:
+            self.DistributedObjectOV_deleted
+        except:
+            self.DistributedObjectOV_deleted = 1
+            self.cr = None
+            self.dclass = None
+
+    def generate(self):
+        """
+        Inheritors should redefine this to take appropriate action on generate
+        """
+        assert self.notify.debugStateCall(self)
+        self.activeState = ESGenerating
+        # this has already been set at this point
+        #self.cr.storeObjectLocation(self.doId, self.parentId, self.zoneId)
+
+    def generateInit(self):
+        """
+        This method is called when the DistributedObjectOV is first introduced
+        to the world... Not when it is pulled from the cache.
+        """
+        self.activeState = ESGenerating
+
+    def getDoId(self):
+        """
+        Return the distributed object id
+        """
+        return self.doId
+
+    def updateRequiredFields(self, dclass, di):
+        dclass.receiveUpdateBroadcastRequired(self, di)
+        self.announceGenerate()
+
+    def updateAllRequiredFields(self, dclass, di):
+        dclass.receiveUpdateAllRequired(self, di)
+        self.announceGenerate()
+
+    def updateRequiredOtherFields(self, dclass, di):
+        # First, update the required fields
+        dclass.receiveUpdateBroadcastRequiredOwner(self, di)
+
+        # Announce generate after updating all the required fields,
+        # but before we update the non-required fields.
+        self.announceGenerate()
+
+        dclass.receiveUpdateOther(self, di)
+
+    def sendUpdate(self, fieldName, args = [], sendToId = None):
+        if self.cr:
+            dg = self.dclass.clientFormatUpdate(
+                fieldName, sendToId or self.doId, args)
+            self.cr.send(dg)
+        else:
+            self.notify.warning("sendUpdate failed, because self.cr is not set")
+
+    def taskName(self, taskString):
+        return ('%s-%s-OV' % (taskString, self.getDoId()))
+
+    def uniqueName(self, idString):
+        return ('%s-%s-OV' % (idString, self.getDoId()))

+ 557 - 0
direct/src/distributed/DistributedObjectUD.py

@@ -0,0 +1,557 @@
+"""DistributedObjectUD module: contains the DistributedObjectUD class"""
+
+from direct.directnotify.DirectNotifyGlobal import *
+from direct.showbase import PythonUtil
+from direct.showbase.DirectObject import DirectObject
+from pandac.PandaModules import *
+from PyDatagram import PyDatagram
+from PyDatagramIterator import PyDatagramIterator
+
+class DistributedObjectUD(DirectObject):
+    notify = directNotify.newCategory("DistributedObjectUD")
+    QuietZone = 1
+
+    def __init__(self, air):
+        try:
+            self.DistributedObjectUD_initialized
+        except:
+            self.DistributedObjectUD_initialized = 1
+
+            self.accountName=''
+            # Record the repository
+            self.air = air
+            # Record our parentId and zoneId
+            self.parentId = None
+            self.zoneId = None
+
+            # Record our distributed class
+            className = self.__class__.__name__
+            self.dclass = self.air.dclassesByName[className]
+            # init doId pre-allocated flag
+            self.__preallocDoId = 0
+
+            # used to track zone changes across the quiet zone
+            # NOTE: the quiet zone is defined in OTP, but we need it
+            # here.
+            self.lastNonQuietZone = None
+
+            self._DOUD_requestedDelete = False
+
+            # These are used to implement beginBarrier().
+            self.__nextBarrierContext = 0
+            self.__barriers = {}
+
+            self.__generated = False
+
+    # Uncomment if you want to debug DO leaks
+    #def __del__(self):
+    #    """
+    #    For debugging purposes, this just prints out what got deleted
+    #    """
+    #    print ("Destructing: " + self.__class__.__name__)
+    
+    if __debug__:
+        def status(self, indent=0):
+            """
+            print out doId(parentId,zoneId) className
+                and conditionally show generated, disabled, neverDisable,
+                or cachable"
+            """
+            spaces=' '*(indent+2)
+            try:
+                print "%s%s:"%(
+                    ' '*indent, self.__class__.__name__)
+                print "%sfrom DistributedObject doId:%s, parent:%s, zone:%s"%(
+                    spaces, 
+                    self.doId, self.parentId, self.zoneId),
+                flags=[]
+                if self.__generated:
+                    flags.append("generated")
+                if self.air == None:
+                    flags.append("deleted")
+                if len(flags):
+                    print "(%s)"%(" ".join(flags),),
+                print
+            except Exception, e: print "%serror printing status"%(spaces,), e
+
+    def getDeleteEvent(self):
+        # this is sent just before we get deleted
+        if hasattr(self, 'doId'):
+            return 'distObjDelete-%s' % self.doId
+        return None
+
+    def sendDeleteEvent(self):
+        # this is called just before we get deleted
+        delEvent = self.getDeleteEvent()
+        if delEvent:
+            messenger.send(delEvent)
+
+    def delete(self):
+        """
+        Inheritors should redefine this to take appropriate action on delete
+        Note that this may be called multiple times if a class inherits
+        from DistributedObjectUD more than once.
+        """
+        # prevent this code from executing multiple times
+        if self.air is not None:
+            # self.doId may not exist.  The __dict__ syntax works around that.
+            assert(self.notify.debug('delete(): %s' % (self.__dict__.get("doId"))))
+
+            if not self._DOUD_requestedDelete:
+                # this logs every delete that was not requested by us.
+                # TODO: this currently prints warnings for deletes of objects
+                # that we did not create. We need to add a 'locally created'
+                # flag to every object to filter these out.
+                """
+                DistributedObjectUD.notify.warning(
+                    'delete() called but requestDelete never called for %s: %s'
+                    % (self.__dict__.get('doId'), self.__class__.__name__))
+                    """
+                """
+                # print a stack trace so we can detect whether this is the
+                # result of a network msg.
+                # this is slow.
+                from direct.showbase.PythonUtil import StackTrace
+                DistributedObjectUD.notify.warning(
+                    'stack trace: %s' % StackTrace())
+                    """
+            self._DOUD_requestedDelete = False
+
+            # Clean up all the pending barriers.
+            for barrier in self.__barriers.values():
+                barrier.cleanup()
+            self.__barriers = {}
+
+            # Asad: As per Roger's suggestion, turn off the following block until a solution is
+            # Thought out of how to prevent this delete message or to handle this message better
+##             if not hasattr(self, "doNotDeallocateChannel"):
+##                 if self.air:
+##                     self.air.deallocateChannel(self.doId)
+##             self.air = None
+            if hasattr(self, 'parentId'):
+                del self.parentId
+            if hasattr(self, 'zoneId'):
+                del self.zoneId
+            self.__generated = False
+
+    def isDeleted(self):
+        """
+        Returns true if the object has been deleted,
+        or if it is brand new and hasnt yet been generated.
+        """
+        return self.air == None
+
+    def isGenerated(self):
+        """
+        Returns true if the object has been generated
+        """
+        return self.__generated
+
+    def getDoId(self):
+        """
+        Return the distributed object id
+        """
+        return self.doId
+
+    def preAllocateDoId(self):
+        """
+        objects that need to have a doId before they are generated
+        can call this to pre-allocate a doId for the object
+        """
+        assert not self.__preallocDoId
+        self.doId = self.air.allocateChannel()
+        self.__preallocDoId = 1
+
+    def announceGenerate(self):
+        """
+        Called after the object has been generated and all
+        of its required fields filled in. Overwrite when needed.
+        """
+        self.__generated = True
+        messenger.send(self.uniqueName("generate"), [self])
+
+    if wantOtpServer:
+        def addInterest(self, zoneId, note="", event=None):
+            self.air.addInterest(self.getDoId(), zoneId, note, event)
+
+        def b_setLocation(self, parentId, zoneId):
+            self.d_setLocation(parentId, zoneId)
+            self.setLocation(parentId, zoneId)
+
+        def d_setLocation(self, parentId, zoneId):
+            self.air.sendSetLocation(self, parentId, zoneId)
+
+        def setLocation(self, parentId, zoneId):
+            # Prevent Duplicate SetLocations for being Called
+            if (self.parentId == parentId) and (self.zoneId == zoneId):
+                return
+
+            oldParentId = self.parentId
+            oldZoneId = self.zoneId
+            if ((oldParentId != parentId) or
+                (oldZoneId != zoneId)):
+                #print "%s location is now %s, %s (%s)"%(self.doId, parentId, zoneId, self)
+                self.zoneId = zoneId
+                self.parentId = parentId
+                self.air.changeDOZoneInTables(self, parentId, zoneId, oldParentId, oldZoneId)
+                messenger.send(self.getZoneChangeEvent(), [zoneId, oldZoneId])
+                # if we are not going into the quiet zone, send a 'logical' zone
+                # change message
+                if zoneId != DistributedObjectUD.QuietZone:
+                    lastLogicalZone = oldZoneId
+                    if oldZoneId == DistributedObjectUD.QuietZone:
+                        lastLogicalZone = self.lastNonQuietZone
+                    self.handleLogicalZoneChange(zoneId, lastLogicalZone)
+                    self.lastNonQuietZone = zoneId
+            self.air.storeObjectLocation(self.doId, parentId, zoneId)
+
+        # Set the initial values of parentId,zoneId
+        def setInitLocation(self, parentId, zoneId):
+            self.parentId=parentId
+            self.zoneId=zoneId
+            
+        def getLocation(self):
+            try:
+                if self.parentId <= 0 and self.zoneId <= 0:
+                    return None
+                # This is a -1 stuffed into a uint32
+                if self.parentId == 0xffffffff and self.zoneId == 0xffffffff:
+                    return None
+                return (self.parentId, self.zoneId)
+            except AttributeError:
+                return None
+
+    else:
+        # NON OTP
+        def handleZoneChange(self, newZoneId, oldZoneId):
+            self.zoneId = newZoneId
+            self.air.changeDOZoneInTables(self, newZoneId, oldZoneId)
+            messenger.send(self.getZoneChangeEvent(), [newZoneId, oldZoneId])
+            # if we are not going into the quiet zone, send a 'logical' zone change
+            # message
+            if newZoneId != DistributedObjectUD.QuietZone:
+                lastLogicalZone = oldZoneId
+                if oldZoneId == DistributedObjectUD.QuietZone:
+                    lastLogicalZone = self.lastNonQuietZone
+                self.handleLogicalZoneChange(newZoneId, lastLogicalZone)
+                self.lastNonQuietZone = newZoneId
+
+    def updateRequiredFields(self, dclass, di):
+        dclass.receiveUpdateBroadcastRequired(self, di)
+        self.announceGenerate()
+
+    def updateAllRequiredFields(self, dclass, di):
+        dclass.receiveUpdateAllRequired(self, di)
+        self.announceGenerate()
+
+    def updateRequiredOtherFields(self, dclass, di):
+        dclass.receiveUpdateBroadcastRequired(self, di)
+        # Announce generate after updating all the required fields,
+        # but before we update the non-required fields.
+        self.announceGenerate()
+        dclass.receiveUpdateOther(self, di)
+
+    def updateAllRequiredOtherFields(self, dclass, di):
+        dclass.receiveUpdateAllRequired(self, di)
+        # Announce generate after updating all the required fields,
+        # but before we update the non-required fields.
+        self.announceGenerate()
+        dclass.receiveUpdateOther(self, di)
+
+    def sendSetZone(self, zoneId):
+        self.air.sendSetZone(self, zoneId)
+
+    def getZoneChangeEvent(self):
+        # this event is generated whenever this object changes zones.
+        # arguments are newZoneId, oldZoneId
+        # includes the quiet zone.
+        return 'DOChangeZone-%s' % self.doId
+    def getLogicalZoneChangeEvent(self):
+        # this event is generated whenever this object changes to a
+        # non-quiet-zone zone.
+        # arguments are newZoneId, oldZoneId
+        # does not include the quiet zone.
+        return 'DOLogicalChangeZone-%s' % self.doId
+
+    def handleLogicalZoneChange(self, newZoneId, oldZoneId):
+        """this function gets called as if we never go through the
+        quiet zone. Note that it is called once you reach the newZone,
+        and not at the time that you leave the oldZone."""
+        messenger.send(self.getLogicalZoneChangeEvent(),
+                       [newZoneId, oldZoneId])
+
+    def getRender(self):
+        # note that this will return a different node if we change zones
+        return self.air.getRender(self.zoneId)
+
+    def getParentMgr(self):
+        return self.air.getParentMgr(self.zoneId)
+
+    def getCollTrav(self):
+        return self.air.getCollTrav(self.zoneId)
+
+    def sendUpdate(self, fieldName, args = []):
+        assert self.notify.debugStateCall(self)
+        if self.air:
+            self.air.sendUpdate(self, fieldName, args)
+
+    if wantOtpServer:
+        def GetPuppetConnectionChannel(self, doId):
+            return doId + (1L << 32)
+
+        def GetAccountIDFromChannelCode(self, channel):
+            return channel >> 32
+
+        def GetAvatarIDFromChannelCode(self, channel):
+            return channel & 0xffffffffL
+
+        def sendUpdateToAvatarId(self, avId, fieldName, args):
+            assert self.notify.debugStateCall(self)
+            channelId = self.GetPuppetConnectionChannel(avId)
+            self.sendUpdateToChannel(channelId, fieldName, args)
+    else:
+        def sendUpdateToAvatarId(self, avId, fieldName, args):
+            assert self.notify.debugStateCall(self)
+            channelId = avId + 1
+            self.sendUpdateToChannel(channelId, fieldName, args)
+
+    def sendUpdateToChannel(self, channelId, fieldName, args):
+        assert self.notify.debugStateCall(self)
+        if self.air:
+            self.air.sendUpdateToChannel(self, channelId, fieldName, args)
+
+    if wantOtpServer:
+        def generateWithRequired(self, zoneId, optionalFields=[]):
+            assert self.notify.debugStateCall(self)
+            # have we already allocated a doId?
+            if self.__preallocDoId:
+                self.__preallocDoId = 0
+                return self.generateWithRequiredAndId(
+                    self.doId, zoneId, optionalFields)
+
+            # The repository is the one that really does the work
+            parentId = self.air.districtId
+            self.parentId = parentId
+            self.zoneId = zoneId
+            self.air.generateWithRequired(self, parentId, zoneId, optionalFields)
+            self.generate()
+    else:
+        def generateWithRequired(self, zoneId, optionalFields=[]):
+            assert self.notify.debugStateCall(self)
+            # have we already allocated a doId?
+            if self.__preallocDoId:
+                self.__preallocDoId = 0
+                return self.generateWithRequiredAndId(
+                    self.doId, zoneId, optionalFields)
+
+            # The repository is the one that really does the work
+            self.air.generateWithRequired(self, zoneId, optionalFields)
+            self.zoneId = zoneId
+            self.generate()
+
+    # this is a special generate used for estates, or anything else that
+    # needs to have a hard coded doId as assigned by the server
+    if wantOtpServer:
+        def generateWithRequiredAndId(self, doId, parentId, zoneId, optionalFields=[]):
+            assert self.notify.debugStateCall(self)
+            # have we already allocated a doId?
+            if self.__preallocDoId:
+                assert doId == self.__preallocDoId
+                self.__preallocDoId = 0
+
+            # The repository is the one that really does the work
+            self.air.generateWithRequiredAndId(self, doId, parentId, zoneId, optionalFields)
+            self.parentId = parentId
+            self.zoneId = zoneId
+            self.generate()
+            self.announceGenerate()
+    else:
+        def generateWithRequiredAndId(self, doId, zoneId, optionalFields=[]):
+            assert self.notify.debugStateCall(self)
+            # have we already allocated a doId?
+            if self.__preallocDoId:
+                assert doId == self.__preallocDoId
+                self.__preallocDoId = 0
+
+            # The repository is the one that really does the work
+            self.air.generateWithRequiredAndId(self, doId, zoneId, optionalFields)
+            self.zoneId = zoneId
+            self.generate()
+            self.announceGenerate()
+
+    if wantOtpServer:
+        def generateOtpObject(self, parentId, zoneId, optionalFields=[], doId=None):
+            assert self.notify.debugStateCall(self)
+            # have we already allocated a doId?
+            if self.__preallocDoId:
+                assert doId is None or doId == self.__preallocDoId
+                doId=self.__preallocDoId
+                self.__preallocDoId = 0
+
+            # Assign it an id
+            if doId is None:
+                self.doId = self.air.allocateChannel()
+            else:
+                self.doId = doId
+            # Put the new DO in the dictionaries
+            self.air.addDOToTables(self, location=(parentId,zoneId))
+            # Send a generate message
+            self.sendGenerateWithRequired(self.air, parentId, zoneId, optionalFields)
+
+            assert not hasattr(self, 'parentId') or self.parentId is None
+            self.parentId = parentId
+            self.zoneId = zoneId
+            self.generate()
+
+    def generate(self):
+        """
+        Inheritors should put functions that require self.zoneId or
+        other networked info in this function.
+        """
+        assert self.notify.debugStateCall(self)
+        if wantOtpServer:
+            self.air.storeObjectLocation(self.doId, self.parentId, self.zoneId)
+
+    if wantOtpServer:
+        def generateInit(self, repository=None):
+            """
+            First generate (not from cache).
+            """
+            assert self.notify.debugStateCall(self)
+
+        def generateTargetChannel(self, repository):
+            """
+            Who to send this to for generate messages
+            """
+            if hasattr(self, "dbObject"):
+                return self.doId
+            return repository.serverId
+
+    def sendGenerateWithRequired(self, repository, parentId, zoneId, optionalFields=[]):
+        assert self.notify.debugStateCall(self)
+        if not wantOtpServer:
+            dg = self.dclass.aiFormatGenerate(
+                    self, self.doId, 0, zoneId,
+                    repository.districtId,
+                    repository.ourChannel,
+                    optionalFields)
+        else:
+            dg = self.dclass.aiFormatGenerate(
+                    self, self.doId, parentId, zoneId,
+                    #repository.serverId,
+                    self.generateTargetChannel(repository),
+                    repository.ourChannel,
+                    optionalFields)
+        repository.send(dg)
+
+    def initFromServerResponse(self, valDict):
+        assert self.notify.debugStateCall(self)
+        # This is a special method used for estates, etc., which get
+        # their fields set from the database indirectly by way of the
+        # UD.  The input parameter is a dictionary of field names to
+        # datagrams that describes the initial field values from the
+        # database.
+
+        dclass = self.dclass
+        for key, value in valDict.items():
+            # Update the field
+            dclass.directUpdate(self, key, value)
+
+    def requestDelete(self):
+        assert self.notify.debugStateCall(self)
+        if not self.air:
+            doId = "none"
+            if hasattr(self, "doId"):
+                doId = self.doId
+            self.notify.warning("Tried to delete a %s (doId %s) that is already deleted" % (self.__class__, doId))
+            return
+        self.air.requestDelete(self)
+        self._DOUD_requestedDelete = True
+
+    def taskName(self, taskString):
+        return (taskString + "-" + str(self.getDoId()))
+
+    def uniqueName(self, idString):
+        return (idString + "-" + str(self.getDoId()))
+
+    def validate(self, avId, bool, msg):
+        if not bool:
+            self.air.writeServerEvent('suspicious', avId, msg)
+            self.notify.warning('validate error: avId: %s -- %s' % (avId, msg))
+        return bool
+
+    def beginBarrier(self, name, avIds, timeout, callback):
+        # Begins waiting for a set of avatars.  When all avatars in
+        # the list have reported back in or the callback has expired,
+        # calls the indicated callback with the list of toons that
+        # made it through.  There may be multiple barriers waiting
+        # simultaneously on different lists of avatars, although they
+        # should have different names.
+
+        from toontown.ai import ToonBarrier
+        context = self.__nextBarrierContext
+        # We assume the context number is passed as a uint16.
+        self.__nextBarrierContext = (self.__nextBarrierContext + 1) & 0xffff
+
+        assert(self.notify.debug('beginBarrier(%s, %s, %s, %s)' % (context, name, avIds, timeout)))
+
+        if avIds:
+            barrier = ToonBarrier.ToonBarrier(
+                name, self.uniqueName(name), avIds, timeout,
+                doneFunc = PythonUtil.Functor(self.__barrierCallback, context, callback))
+            self.__barriers[context] = barrier
+
+            # Send the context number to each involved client.
+            self.sendUpdate("setBarrierData", [self.__getBarrierData()])
+        else:
+            # No avatars; just call the callback immediately.
+            callback(avIds)
+
+        return context
+
+    def __getBarrierData(self):
+        # Returns the barrier data formatted for sending to the
+        # clients.  This lists all of the current outstanding barriers
+        # and the avIds waiting for them.
+        data = []
+        for context, barrier in self.__barriers.items():
+            toons = barrier.pendingToons
+            if toons:
+                data.append((context, barrier.name, toons))
+        return data
+
+    def ignoreBarrier(self, context):
+        # Aborts a previously-set barrier.  The context is the return
+        # value from the previous call to beginBarrier().
+        barrier = self.__barriers.get(context)
+        if barrier:
+            barrier.cleanup()
+            del self.__barriers[context]
+
+    def setBarrierReady(self, context):
+        # Generated by the clients to check in after a beginBarrier()
+        # call.
+        avId = self.air.GetAvatarIDFromSender()
+        assert(self.notify.debug('setBarrierReady(%s, %s)' % (context, avId)))
+        barrier = self.__barriers.get(context)
+        if barrier == None:
+            # This may be None if a client was slow and missed an
+            # earlier timeout.  Too bad.
+            return
+
+        barrier.clear(avId)
+
+    def __barrierCallback(self, context, callback, avIds):
+        assert(self.notify.debug('barrierCallback(%s, %s)' % (context, avIds)))
+        # The callback that is generated when a barrier is completed.
+        barrier = self.__barriers.get(context)
+        if barrier:
+            barrier.cleanup()
+            del self.__barriers[context]
+            callback(avIds)
+        else:
+            self.notify.warning("Unexpected completion from barrier %s" % (context))
+
+    def isGridParent(self):
+        # If this distributed object is a DistributedGrid return 1.  0 by default
+        return 0

+ 2 - 3
direct/src/distributed/DistributedSmoothNode.py

@@ -4,7 +4,7 @@ from pandac.PandaModules import *
 from ClockDelta import *
 import DistributedNode
 import DistributedSmoothNodeBase
-from direct.task import Task
+from direct.task.Task import cont
 
 # This number defines our tolerance for out-of-sync telemetry packets.
 # If a packet appears to have originated from more than MaxFuture
@@ -107,7 +107,7 @@ class DistributedSmoothNode(DistributedNode.DistributedNode,
             
     def doSmoothTask(self, task):
         self.smoothPosition()
-        return Task.cont
+        return cont
 
     def wantsSmoothing(self):
         # Override this function to return 0 if this particular kind
@@ -216,7 +216,6 @@ class DistributedSmoothNode(DistributedNode.DistributedNode,
         self.setComponentP(p)
         self.setComponentR(r)
         self.setComponentTLive(timestamp)
-        return
 
     ### component set pos and hpr functions ###
 

+ 48 - 12
direct/src/distributed/DoCollectionManager.py

@@ -1,5 +1,6 @@
 #hack:
-BAD_DO_ID = BAD_ZONE_ID = -1
+BAD_DO_ID = BAD_ZONE_ID = 0xFFFFFFFF
+BAD_CHANNEL_ID = 0xFFFFFFFFFFFFFFFF
 
 class DoCollectionManager:
     def __init__(self):
@@ -8,12 +9,28 @@ class DoCollectionManager:
         # for OTP: (parentId, zoneId) to dict of doId->DistributedObjectAI
         # for NON-OTP: zoneId to dict of doId->DistributedObjectAI
         self.zoneId2doIds={}
+        if self.hasOwnerView():
+            # Dict of {DistributedObject ids : DistributedObjects} for 'owner' views of objects
+            self.doId2ownerView = {}
         if wantOtpServer:
             # Dict of {
             #   parent DistributedObject id: 
             #     { zoneIds : [child DistributedObject ids] }}
             self.__doHierarchy = {}
 
+    def getDo(self, doId):
+        return self.doId2do.get(doId)
+    def getOwnerView(self, doId):
+        assert self.hasOwnerView()
+        return self.doId2ownerView.get(doId)
+
+    def getDoTable(self, ownerView):
+        if ownerView:
+            assert self.hasOwnerView()
+            return self.doId2ownerView
+        else:
+            return self.doId2do
+
     def doFind(self, str):
         """
         Returns list of distributed objects with matching str in value.
@@ -49,6 +66,22 @@ class DoCollectionManager:
                     distObj.dclass.getName(),
                     distObj.__dict__.get("name"))
 
+    def getDoList(self, parentId, zoneId=None, classType=None):
+        """
+        parentId is any distributed object id.
+        zoneId is a uint32, defaults to None (all zones).  Try zone 2 if
+            you're not sure which zone to use (0 is a bad/null zone and 
+            1 has had reserved use in the past as a no messages zone, while
+            2 has traditionally been a global, uber, misc stuff zone).
+        dclassType is a distributed class type filter, defaults 
+            to None (no filter).
+        
+        If dclassName is None then all objects in the zone are returned;
+        otherwise the list is filtered to only include objects of that type.
+        """
+        return [self.doId2do.get(i)
+            for i in self.getDoIdList(parentId, zoneId, classType)]
+
     def getDoIdList(self, parentId, zoneId=None, classType=None):
         """
         parentId is any distributed object id.
@@ -195,24 +228,27 @@ class DoCollectionManager:
                 objList.remove(objId)
     
     if wantOtpServer:
-        def addDOToTables(self, do, location=None):
+        def addDOToTables(self, do, location=None, ownerView=False):
             assert self.notify.debugStateCall(self)
             assert not hasattr(do, "isQueryAllResponse") or not do.isQueryAllResponse
-            if location is None:
-                location = (do.parentId, do.zoneId)
+            if not ownerView:
+                if location is None:
+                    location = (do.parentId, do.zoneId)
 
-            #assert do.doId not in self.doId2do
-            if do.doId in self.doId2do:
+            doTable = self.getDoTable(ownerView)
+            
+            #assert do.doId not in doTable
+            if do.doId in doTable:
                 print "ignoring repeated object %s" % (do.doId)
                 return
             
-            self.doId2do[do.doId]=do
+            doTable[do.doId]=do
 
-            if self.isValidLocationTuple(location):
-                assert hasattr(do, "isGlobalDistObj") or (
-                    do.doId not in self.zoneId2doIds.get(location,{}))
-                self.zoneId2doIds.setdefault(location, {})
-                self.zoneId2doIds[location][do.doId]=do
+            if not ownerView:
+                if self.isValidLocationTuple(location):
+                    assert do.doId not in self.zoneId2doIds.get(location,{})
+                    self.zoneId2doIds.setdefault(location, {})
+                    self.zoneId2doIds[location][do.doId]=do
 
         def isValidLocationTuple(self, location):
             return (location is not None

+ 8 - 8
direct/src/distributed/DoInterestManager.py

@@ -9,7 +9,6 @@ p.s. A great deal of this code is just code moved from ClientRepository.py.
 
 from pandac.PandaModules import *
 from MsgTypes import *
-from direct.directnotify import DirectNotifyGlobal
 from direct.showbase.PythonUtil import *
 from direct.showbase import DirectObject
 from PyDatagram import PyDatagram
@@ -20,7 +19,8 @@ class DoInterestManager(DirectObject.DirectObject):
     Top level Interest Manager
     """
     if __debug__:
-        notify = DirectNotifyGlobal.directNotify.newCategory("DoInterestManager")
+        from direct.directnotify.DirectNotifyGlobal import directNotify
+        notify = directNotify.newCategory("DoInterestManager")
 
     _interestIdAssign = 1
     _interestIdScopes = 100
@@ -201,23 +201,23 @@ class DoInterestManager(DirectObject.DirectObject):
         action based on the ID, Context
         """
         assert DoInterestManager.notify.debugCall()
-        id = di.getUint16()
+        interestId = di.getUint16()
         scope = di.getUint32()
-        expect_scope = self.getInterestScopeId(id)
+        expect_scope = self.getInterestScopeId(interestId)
         DoInterestManager.notify.debug(
-            "handleInterestDoneMessage--> Received ID:%s Scope:%s"%(id,scope))
+            "handleInterestDoneMessage--> Received ID:%s Scope:%s"%(interestId, scope))
         if expect_scope == scope:
             DoInterestManager.notify.debug(
                 "handleInterestDoneMessage--> Scope Match:%s Scope:%s"
-                %(id,scope))
-            event = self.getInterestScopeEvent(id)
+                %(interestId, scope))
+            event = self.getInterestScopeEvent(interestId)
             if event is not None:
                 DoInterestManager.notify.debug(
                     "handleInterestDoneMessage--> Send Event : %s"%(event))
                 messenger.send(event)
             else:
                 DoInterestManager.notify.debug("handleInterestDoneMessage--> No Event ")
-            self._ponderRemoveFlaggedInterest(id)
+            self._ponderRemoveFlaggedInterest(interestId)
         else:
             DoInterestManager.notify.debug(
                 "handleInterestDoneMessage--> Scope MisMatch :%s :%s"

+ 4 - 0
direct/src/distributed/MsgTypes.py

@@ -25,6 +25,8 @@ CLIENT_OBJECT_UPDATE_FIELD =                 24
 CLIENT_OBJECT_UPDATE_FIELD_RESP =            24
 CLIENT_OBJECT_DISABLE =                      25
 CLIENT_OBJECT_DISABLE_RESP =                 25
+CLIENT_OBJECT_DISABLE_OWNER =                26
+CLIENT_OBJECT_DISABLE_OWNER_RESP =           26
 CLIENT_OBJECT_DELETE =                       27
 CLIENT_OBJECT_DELETE_RESP =                  27
 if not wantOtpServer:        
@@ -37,6 +39,8 @@ CLIENT_CREATE_OBJECT_REQUIRED =              34
 CLIENT_CREATE_OBJECT_REQUIRED_RESP =         34
 CLIENT_CREATE_OBJECT_REQUIRED_OTHER =        35
 CLIENT_CREATE_OBJECT_REQUIRED_OTHER_RESP =   35
+CLIENT_CREATE_OBJECT_REQUIRED_OTHER_OWNER      = 36
+CLIENT_CREATE_OBJECT_REQUIRED_OTHER_OWNER_RESP = 36
 
 CLIENT_REQUEST_GENERATES =                   36
 

+ 6 - 5
direct/src/distributed/PyDatagram.py

@@ -28,30 +28,31 @@ class PyDatagram(Datagram):
         STBlob32: (Datagram.addString32, None),
         }
 
-        
+    #def addChannel(self, channelId):
+    #    ...
     addChannel = Datagram.addUint64
     
-    def AddServerHeader(self, channel, sender, code):
+    def addServerHeader(self, channel, sender, code):
         self.addInt8(1)
         self.addChannel(channel)
         self.addChannel(sender)
         self.addUint16(code)
     
     
-    def AddOldServerHeader(self, channel, sender, code):
+    def addOldServerHeader(self, channel, sender, code):
         self.addChannel(channel)
         self.addChannel(sender)
         self.addChannel('A')
         self.addUint16(code)
     
     
-    def AddServerControlHeader(self,   code):
+    def addServerControlHeader(self,   code):
         self.addInt8(1)
         self.addChannel(CONTROL_MESSAGE)
         self.addUint16(code)
     
     
-    def AddOldServerControlHeader(self,   code):
+    def addOldServerControlHeader(self,   code):
         self.addChannel(CONTROL_MESSAGE)
         self.addUint16(code)
     

+ 11 - 0
direct/src/distributed/cConnectionRepository.I

@@ -28,6 +28,17 @@ get_dc_file() {
   return _dc_file;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: CConnectionRepository::has_owner_view
+//       Access: Published
+//  Description: Returns true if this repository can have 'owner'
+//               views of distributed objects.
+////////////////////////////////////////////////////////////////////
+INLINE bool CConnectionRepository::
+has_owner_view() const {
+  return _has_owner_view;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: CConnectionRepository::set_client_datagram
 //       Access: Published

+ 123 - 4
direct/src/distributed/cConnectionRepository.cxx

@@ -41,7 +41,7 @@ PStatCollector CConnectionRepository::_update_pcollector("App:Show code:readerPo
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 CConnectionRepository::
-CConnectionRepository() :
+CConnectionRepository(bool has_owner_view) :
 #ifdef HAVE_PYTHON
   _python_repository(NULL),
 #endif
@@ -57,7 +57,8 @@ CConnectionRepository() :
   _verbose(distributed_cat.is_spam()),
 //  _msg_channels(),
   _msg_sender(0),
-  _msg_type(0)
+  _msg_type(0),
+  _has_owner_view(has_owner_view)
 {
 #if defined(HAVE_NSPR) && defined(SIMULATE_NETWORK_DELAY)
   if (min_lag != 0.0 || max_lag != 0.0) {
@@ -246,8 +247,14 @@ check_datagram() {
 #ifdef HAVE_PYTHON
     case CLIENT_OBJECT_UPDATE_FIELD:
     case STATESERVER_OBJECT_UPDATE_FIELD:
-      if (!handle_update_field()) {
-        return false;
+      if (_has_owner_view) {
+	if (!handle_update_field_owner()) {
+	  return false;
+	}
+      } else {
+	if (!handle_update_field()) {
+	  return false;
+	}
       }
       break;
 #endif  // HAVE_PYTHON
@@ -514,6 +521,118 @@ handle_update_field() {
         return false;
       }
     }
+
+  }
+  #endif  // HAVE_PYTHON  
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CConnectionRepository::handle_update_field_owner
+//       Access: Private
+//  Description: Directly handles an update message on a field.
+//               Supports 'owner' views of objects, separate from 'visible'
+//               view, and forwards fields to the appropriate view(s) based
+//               on DC flags.  Python never touches the datagram; it just
+//               gets its distributed method called with the appropriate
+//               parameters.  Returns true if everything is ok, false if
+//               there was an error processing the field's update method.
+////////////////////////////////////////////////////////////////////
+bool CConnectionRepository::
+handle_update_field_owner() {
+  #ifdef HAVE_PYTHON
+  PStatTimer timer(_update_pcollector);
+  unsigned int do_id = _di.get_uint32();
+  if (_python_repository != (PyObject *)NULL) {
+    PyObject *doId2do =
+      PyObject_GetAttrString(_python_repository, "doId2do");
+    nassertr(doId2do != NULL, false);
+
+    PyObject *doId2ownerView =
+      PyObject_GetAttrString(_python_repository, "doId2ownerView");
+    nassertr(doId2ownerView != NULL, false);
+
+    #ifdef USE_PYTHON_2_2_OR_EARLIER
+    PyObject *doId = PyInt_FromLong(do_id);
+    #else
+    PyObject *doId = PyLong_FromUnsignedLong(do_id);
+    #endif
+
+    // pass the update to the owner view first
+    PyObject *distobjOV = PyDict_GetItem(doId2ownerView, doId);
+    Py_DECREF(doId2ownerView);
+
+    if (distobjOV != NULL) {
+      PyObject *dclass_obj = PyObject_GetAttrString(distobjOV, "dclass");
+      nassertr(dclass_obj != NULL, false);
+
+      PyObject *dclass_this = PyObject_GetAttrString(dclass_obj, "this");
+      Py_DECREF(dclass_obj);
+      nassertr(dclass_this != NULL, false);
+
+      DCClass *dclass = (DCClass *)PyInt_AsLong(dclass_this);
+      Py_DECREF(dclass_this);
+
+      // check if we should forward this update to the owner view
+      DCPacker packer;
+      packer.set_unpack_data(_di.get_remaining_bytes());
+      int field_id = packer.raw_unpack_uint16();
+      DCField *field = dclass->get_field_by_index(field_id);
+      if (field->is_ownrecv()) {
+	// It's a good idea to ensure the reference count to distobjOV is
+	// raised while we call the update method--otherwise, the update
+	// method might get into trouble if it tried to delete the
+	// object from the doId2do map.
+	Py_INCREF(distobjOV);
+	// make a copy of the datagram iterator so that we can use the main
+	// iterator for the non-owner update
+	DatagramIterator _odi(_di);
+	dclass->receive_update(distobjOV, _odi); 
+	Py_DECREF(distobjOV);
+      
+	if (PyErr_Occurred()) {
+	  return false;
+	}
+      }
+    }
+
+    // now pass the update to the visible view
+    PyObject *distobj = PyDict_GetItem(doId2do, doId);
+    Py_DECREF(doId);
+    Py_DECREF(doId2do);
+
+    if (distobj != NULL) {
+      PyObject *dclass_obj = PyObject_GetAttrString(distobj, "dclass");
+      nassertr(dclass_obj != NULL, false);
+
+      PyObject *dclass_this = PyObject_GetAttrString(dclass_obj, "this");
+      Py_DECREF(dclass_obj);
+      nassertr(dclass_this != NULL, false);
+
+      DCClass *dclass = (DCClass *)PyInt_AsLong(dclass_this);
+      Py_DECREF(dclass_this);
+
+      // check if we should forward this update to the owner view
+      DCPacker packer;
+      packer.set_unpack_data(_di.get_remaining_bytes());
+      int field_id = packer.raw_unpack_uint16();
+      DCField *field = dclass->get_field_by_index(field_id);
+      if (true) {//field->is_broadcast()) {
+	// It's a good idea to ensure the reference count to distobj is
+	// raised while we call the update method--otherwise, the update
+	// method might get into trouble if it tried to delete the
+	// object from the doId2do map.
+	Py_INCREF(distobj);
+	dclass->receive_update(distobj, _di); 
+	Py_DECREF(distobj);
+      
+	if (PyErr_Occurred()) {
+	  return false;
+	}
+      }
+    }
+
   }
   #endif  // HAVE_PYTHON  
 

+ 5 - 1
direct/src/distributed/cConnectionRepository.h

@@ -55,11 +55,13 @@ class SocketStream;
 ////////////////////////////////////////////////////////////////////
 class EXPCL_DIRECT CConnectionRepository {
 PUBLISHED:
-  CConnectionRepository();
+  CConnectionRepository(bool has_owner_view=false);
   ~CConnectionRepository();
 
   INLINE DCFile &get_dc_file();
 
+  INLINE bool has_owner_view() const;
+
   INLINE void set_client_datagram(bool client_datagram);
   INLINE bool get_client_datagram() const;
 
@@ -116,6 +118,7 @@ PUBLISHED:
 private:
   bool do_check_datagram();
   bool handle_update_field();
+  bool handle_update_field_owner();
 
 #ifndef NDEBUG
   void describe_message(ostream &out, const string &prefix, 
@@ -138,6 +141,7 @@ private:
 #endif
 
   DCFile _dc_file;
+  bool _has_owner_view;
   bool _client_datagram;
   bool _simulated_disconnect;
   bool _verbose;

+ 0 - 1
direct/src/gui/DirectScrolledList.py

@@ -322,7 +322,6 @@ class DirectScrolledList(DirectFrame):
             self.refresh()
         if(type(item) == types.InstanceType):
             return item.itemID  # to pass to scrollToItemID
-        return
 
     def removeItem(self, item, refresh=1):
         """

+ 0 - 3
direct/src/showbase/PythonUtil.py

@@ -738,9 +738,6 @@ def printProfile(filename=PyUtilProfileDefaultFilename,
             s.print_callees(lines)
             s.print_callers(lines)
 
-def capitalizeFirst(str):
-    return '%s%s' % (string.upper(str[0]), str[1:])
-
 def getSetterName(valueName, prefix='set'):
     # getSetterName('color') -> 'setColor'
     # getSetterName('color', 'get') -> 'getColor'

+ 36 - 31
direct/src/showbase/Transitions.py

@@ -12,7 +12,8 @@ class Transitions:
     FadeModelName = "models/misc/fade"
 
     def __init__(self, loader):
-        self.ival = None
+        self.transitionIval = None
+        self.letterboxIval = None
         self.iris = None
         self.fade = None
         self.letterbox = None
@@ -60,15 +61,15 @@ class Transitions:
             # Create a sequence that lerps the color out, then
             # parents the fade to hidden
             self.fade.reparentTo(aspect2d, FADE_SORT_INDEX)
-            self.ival = Sequence(LerpColorInterval(self.fade, t,
+            self.transitionIval = Sequence(LerpColorInterval(self.fade, t,
                                                    color = self.alphaOff,
                                                    startColor = self.alphaOn),
                                  Func(self.fade.detachNode),
                                  name = self.fadeTaskName,
                                  )
             if finishIval:
-                self.ival.append(finishIval)
-            self.ival.start()
+                self.transitionIval.append(finishIval)
+            self.transitionIval.start()
             
     def fadeOut(self, t=0.5, finishIval=None):
         """
@@ -88,14 +89,17 @@ class Transitions:
         else:
             # Create a sequence that lerps the color out, then
             # parents the fade to hidden
-            self.ival = Sequence(LerpColorInterval(self.fade, t,
+            self.transitionIval = Sequence(LerpColorInterval(self.fade, t,
                                                    color = self.alphaOn,
                                                    startColor = self.alphaOff),
                                  name = self.fadeTaskName,
                                  )
             if finishIval:
-                self.ival.append(finishIval)
-            self.ival.start()
+                self.transitionIval.append(finishIval)
+            self.transitionIval.start()
+
+    def fadeOutActive(self):
+        return self.fade and self.fade.getColor()[3] > 0
 
     def fadeScreen(self, alpha=0.5):
         """
@@ -126,9 +130,9 @@ class Transitions:
         """
         Removes any current fade tasks and parents the fade polygon away
         """
-        if self.ival:
-            self.ival.pause()
-            self.ival = None
+        if self.transitionIval:
+            self.transitionIval.pause()
+            self.transitionIval = None
         if self.fade:
             self.fade.detachNode()
 
@@ -160,15 +164,15 @@ class Transitions:
         else:
             self.iris.reparentTo(aspect2d, FADE_SORT_INDEX)
 
-            self.ival = Sequence(LerpScaleInterval(self.iris, t,
+            self.transitionIval = Sequence(LerpScaleInterval(self.iris, t,
                                                    scale = 0.18,
                                                    startScale = 0.01),
                                  Func(self.iris.detachNode),
                                  name = self.irisTaskName,
                                  )
             if finishIval:
-                self.ival.append(finishIval)
-            self.ival.start()
+                self.transitionIval.append(finishIval)
+            self.transitionIval.start()
             
     def irisOut(self, t=0.5, finishIval=None):
         """
@@ -187,7 +191,7 @@ class Transitions:
         else:
             self.iris.reparentTo(aspect2d, FADE_SORT_INDEX)
 
-            self.ival = Sequence(LerpScaleInterval(self.iris, t,
+            self.transitionIval = Sequence(LerpScaleInterval(self.iris, t,
                                                    scale = 0.01,
                                                    startScale = 0.18),
                                  Func(self.iris.detachNode),
@@ -196,16 +200,16 @@ class Transitions:
                                  name = self.irisTaskName,
                                  )
             if finishIval:
-                self.ival.append(finishIval)
-            self.ival.start()
+                self.transitionIval.append(finishIval)
+            self.transitionIval.start()
 
     def noIris(self):
         """
         Removes any current iris tasks and parents the iris polygon away
         """
-        if self.ival:
-            self.ival.pause()
-            self.ival = None
+        if self.transitionIval:
+            self.transitionIval.pause()
+            self.transitionIval = None
         if self.iris != None:
             self.iris.detachNode()
         # Actually we need to remove the fade too,
@@ -218,7 +222,8 @@ class Transitions:
         """
         self.noFade()
         self.noIris()
-        self.noLetterbox()
+        # Letterbox is not really a transition, it is a screen overlay
+        # self.noLetterbox()
 
     ##################################################
     # Letterbox
@@ -258,9 +263,9 @@ class Transitions:
         """
         Removes any current letterbox tasks and parents the letterbox polygon away
         """
-        if self.ival:
-            self.ival.pause()
-            self.ival = None
+        if self.letterboxIval:
+            self.letterboxIval.pause()
+            self.letterboxIval = None
         if self.letterbox != None:
             self.letterbox.detachNode()
 
@@ -268,7 +273,7 @@ class Transitions:
         """
         Move black bars in over t seconds.
         """
-        self.noTransitions()
+        self.noLetterbox()
         self.loadLetterbox()
         if (t == 0):
             self.letterbox.reparentTo(render2d, FADE_SORT_INDEX)
@@ -276,7 +281,7 @@ class Transitions:
             self.letterboxTop.setPos(0,0,0.8)
         else:
             self.letterbox.reparentTo(render2d, FADE_SORT_INDEX)
-            self.ival = Sequence(Parallel(LerpPosInterval(self.letterboxBottom, t,
+            self.letterboxIval = Sequence(Parallel(LerpPosInterval(self.letterboxBottom, t,
                                                           pos = Vec3(0,0,-1),
                                                           startPos = Vec3(0,0,-1.2)),
                                           LerpPosInterval(self.letterboxTop, t,
@@ -289,20 +294,20 @@ class Transitions:
                                  name = self.letterboxTaskName,
                                  )
             if finishIval:
-                self.ival.append(finishIval)
-            self.ival.start()
+                self.letterboxIval.append(finishIval)
+            self.letterboxIval.start()
             
     def letterboxOff(self, t=0.25, finishIval=None):
         """
         Move black bars away over t seconds.
         """
-        self.noTransitions()
+        self.noLetterbox()
         self.loadLetterbox()
         if (t == 0):
             self.letterbox.detachNode()
         else:
             self.letterbox.reparentTo(render2d, FADE_SORT_INDEX)
-            self.ival = Sequence(Parallel(LerpPosInterval(self.letterboxBottom, t,
+            self.letterboxIval = Sequence(Parallel(LerpPosInterval(self.letterboxBottom, t,
                                                           pos = Vec3(0,0,-1.2),
                                                           startPos = Vec3(0,0,-1)),
                                           LerpPosInterval(self.letterboxTop, t,
@@ -315,5 +320,5 @@ class Transitions:
                                  name = self.letterboxTaskName,
                                  )
             if finishIval:
-                self.ival.append(finishIval)
-            self.ival.start()
+                self.letterboxIval.append(finishIval)
+            self.letterboxIval.start()

+ 11 - 1
direct/src/task/Task.py

@@ -760,7 +760,6 @@ class TaskManager:
         signal.signal(signal.SIGINT, signal.default_int_handler)
         if self.fKeyboardInterrupt:
             raise KeyboardInterrupt
-        return
 
     def run(self):
         # Set the clock to have last frame's time in case we were
@@ -783,6 +782,17 @@ class TaskManager:
                     self.step()
                 except KeyboardInterrupt:
                     self.stop()
+                except IOError, (errno, strerror):
+                    # Since upgrading to Python 2.4.1, pausing the execution
+                    # often gives this IOError during the sleep function:
+                    #     IOError: [Errno 4] Interrupted function call
+                    # So, let's just handle that specific exception and stop.
+                    # All other IOErrors should still get raised.
+                    # Only problem: legit IOError 4s will be obfuscated.
+                    if errno == 4:
+                        self.stop()
+                    else:
+                        raise
                 except:
                     if self.extendedExceptions:
                         self.stop()

+ 0 - 1
direct/src/tkwidgets/AppShell.py

@@ -207,7 +207,6 @@ class AppShell(Pmw.MegaWidget, PandaObject):
         self.about = Pmw.AboutDialog(self._hull, 
                                      applicationname=self.appname)
         self.about.withdraw()
-        return None
        
     def toggleBalloon(self):
         if self.toggleBalloonVar.get():