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):
     def startSwapCoordinatorTask(self):
         taskMgr.add(self.swapCoordinator, "clientSwapCoordinator", 51)
         taskMgr.add(self.swapCoordinator, "clientSwapCoordinator", 51)
-        return None
 
 
     def swapCoordinator(self,task):
     def swapCoordinator(self,task):
         self.ready = 1
         self.ready = 1

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

@@ -8,6 +8,10 @@ def ToggleStrafe():
     global BattleStrafe
     global BattleStrafe
     BattleStrafe = not BattleStrafe
     BattleStrafe = not BattleStrafe
 
 
+def SetStrafe(status):
+    global BattleStrafe
+    BattleStrafe = status
+    
 class BattleWalker(GravityWalker.GravityWalker):
 class BattleWalker(GravityWalker.GravityWalker):
     def __init__(self):
     def __init__(self):
         GravityWalker.GravityWalker.__init__(self)
         GravityWalker.GravityWalker.__init__(self)
@@ -109,7 +113,30 @@ class BattleWalker(GravityWalker.GravityWalker):
         elif delH > rMax:
         elif delH > rMax:
             self.avatarNodePath.setH(curH+rMax)
             self.avatarNodePath.setH(curH+rMax)
             self.rotationSpeed=self.avatarControlRotateSpeed
             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:
         # Check to see if we're moving at all:
         self.moving = self.advanceSpeed or self.slideSpeed or self.rotationSpeed or (self.priorParent!=Vec3.zero())
         self.moving = self.advanceSpeed or self.slideSpeed or self.rotationSpeed or (self.priorParent!=Vec3.zero())
         if self.moving:
         if self.moving:
@@ -138,6 +165,7 @@ class BattleWalker(GravityWalker.GravityWalker):
             self.avatarNodePath.setH(self.avatarNodePath.getH()+rotation)
             self.avatarNodePath.setH(self.avatarNodePath.getH()+rotation)
         else:
         else:
             self.vel.set(0.0, 0.0, 0.0)
             self.vel.set(0.0, 0.0, 0.0)
+        """
         if self.moving or jump:
         if self.moving or jump:
             messenger.send("avatarMoving")
             messenger.send("avatarMoving")
         return Task.cont
         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", "alt-arrow_down", "arrow_down-up")
         inputState.watch("reverse", "control-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", "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", "arrow_left", "arrow_left-up")
         inputState.watch("turnLeft", "control-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
 #ifdef HAVE_PYTHON
   _class_def = NULL;
   _class_def = NULL;
+  _owner_class_def = NULL;
 #endif
 #endif
 }
 }
 
 
@@ -84,6 +85,7 @@ DCClass::
 
 
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
   Py_XDECREF(_class_def);
   Py_XDECREF(_class_def);
+  Py_XDECREF(_owner_class_def);
 #endif
 #endif
 }
 }
 
 
@@ -384,6 +386,54 @@ get_class_def() const {
 }
 }
 #endif  // HAVE_PYTHON
 #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
 #ifdef HAVE_PYTHON
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: DCClass::receive_update
 //     Function: DCClass::receive_update
@@ -453,6 +503,48 @@ receive_update_broadcast_required(PyObject *distobj, DatagramIterator &di) const
 }
 }
 #endif  // HAVE_PYTHON
 #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
 #ifdef HAVE_PYTHON
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: DCClass::receive_update_all_required
 //     Function: DCClass::receive_update_all_required
@@ -939,7 +1031,7 @@ ai_format_generate(PyObject *distobj, int do_id,
 #endif  // HAVE_PYTHON
 #endif  // HAVE_PYTHON
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: DCClass::ai_database_generate
+//     Function: DCClass::ai_database_generate_context
 //       Access: Published
 //       Access: Published
 //  Description: Generates a datagram containing the message necessary
 //  Description: Generates a datagram containing the message necessary
 //               to create a new database distributed object from the AI.
 //               to create a new database distributed object from the AI.
@@ -949,6 +1041,50 @@ ai_format_generate(PyObject *distobj, int do_id,
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 Datagram DCClass::
 Datagram DCClass::
 ai_database_generate_context(
 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,
     unsigned int context_id, unsigned int parent_id, unsigned int zone_id,
     CHANNEL_TYPE database_server_id, CHANNEL_TYPE from_channel_id) const 
     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;
   bool has_class_def() const;
   void set_class_def(PyObject *class_def);
   void set_class_def(PyObject *class_def);
   PyObject *get_class_def() const;
   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(PyObject *distobj, DatagramIterator &di) const;
   void receive_update_broadcast_required(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_all_required(PyObject *distobj, DatagramIterator &di) const;
   void receive_update_other(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,                              
   Datagram client_format_generate(PyObject *distobj, int do_id, int zone_id,                              
                                   PyObject *optional_fields) const;
                                   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;
                                 CHANNEL_TYPE database_server_id, CHANNEL_TYPE from_channel_id) const;
   
   
 #endif 
 #endif 
@@ -159,6 +165,7 @@ private:
 
 
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
   PyObject *_class_def;
   PyObject *_class_def;
+  PyObject *_owner_class_def;
 #endif
 #endif
 
 
   friend class DCField;
   friend class DCField;

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

@@ -300,17 +300,6 @@ is_broadcast() const {
   return has_keyword("broadcast");
   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
 //     Function: DCField::is_ram
 //       Access: Published
 //       Access: Published
@@ -366,6 +355,17 @@ is_ownsend() const {
   return has_keyword("ownsend");
   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
 //     Function: DCField::is_airecv
 //       Access: Published
 //       Access: Published

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

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

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

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

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

@@ -50,8 +50,11 @@ class Notifier:
         Notifier.serverDelta = delta + time.timezone - timezone
         Notifier.serverDelta = delta + time.timezone - timezone
 
 
         from pandac.PandaModules import NotifyCategory
         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))
         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):
     def getTime(self):

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

@@ -32,8 +32,12 @@ class DirectCameraControl(PandaObject):
         self.camManipRef = direct.group.attachNewNode('camManipRef')
         self.camManipRef = direct.group.attachNewNode('camManipRef')
         t = CAM_MOVE_DURATION
         t = CAM_MOVE_DURATION
         self.actionEvents = [
         self.actionEvents = [
+            ['DIRECT-mouse1', self.mouseRotateStart],
+            ['DIRECT-mouse1Up', self.mouseFlyStop],
             ['DIRECT-mouse2', self.mouseFlyStart],
             ['DIRECT-mouse2', self.mouseFlyStart],
             ['DIRECT-mouse2Up', self.mouseFlyStop],
             ['DIRECT-mouse2Up', self.mouseFlyStop],
+            ['DIRECT-mouse3', self.mouseDollyStart],
+            ['DIRECT-mouse3Up', self.mouseFlyStop],
             ]
             ]
         self.keyEvents = [
         self.keyEvents = [
             ['c', self.centerCamIn, 0.5],
             ['c', self.centerCamIn, 0.5],
@@ -62,6 +66,8 @@ class DirectCameraControl(PandaObject):
             ]
             ]
         # set this to true to prevent the camera from rolling
         # set this to true to prevent the camera from rolling
         self.lockRoll = False
         self.lockRoll = False
+        # NIK - flag to determine whether to use maya camera controls
+        self.useMayaCamControls = 0
 
 
     def toggleMarkerVis(self):
     def toggleMarkerVis(self):
         if direct.cameraControl.coaMarker.isHidden():
         if direct.cameraControl.coaMarker.isHidden():
@@ -69,28 +75,52 @@ class DirectCameraControl(PandaObject):
         else:
         else:
             direct.cameraControl.coaMarker.hide()
             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):
     def mouseFlyStart(self, modifiers):
         # Record undo point
         # Record undo point
         direct.pushUndo([direct.camera])
         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
             # Hide the marker for this kind of motion
             self.coaMarker.hide()
             self.coaMarker.hide()
             # Record time of start of mouse interaction
             # Record time of start of mouse interaction
             self.startT= globalClock.getFrameTime()
             self.startT= globalClock.getFrameTime()
             self.startF = globalClock.getFrameCount()
             self.startF = globalClock.getFrameCount()
             # Start manipulation
             # Start manipulation
-            self.spawnXZTranslateOrHPanYZoom()
-            # END MOUSE IN CENTRAL REGION
+            self.spawnXZTranslate()
         else:
         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:
             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):
     def mouseFlyStop(self):
         taskMgr.remove('manipulateCamera')
         taskMgr.remove('manipulateCamera')
@@ -119,6 +149,15 @@ class DirectCameraControl(PandaObject):
         # Resize it
         # Resize it
         self.updateCoaMarkerSize()
         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):
     def spawnXZTranslateOrHPanYZoom(self):
         # Kill any existing tasks
         # Kill any existing tasks
         taskMgr.remove('manipulateCamera')
         taskMgr.remove('manipulateCamera')
@@ -183,6 +222,11 @@ class DirectCameraControl(PandaObject):
         return Task.cont
         return Task.cont
 
 
     def HPanYZoomTask(self,state):
     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:
         if direct.fControl:
             moveDir = Vec3(self.coaMarker.getPos(direct.camera))
             moveDir = Vec3(self.coaMarker.getPos(direct.camera))
             # If marker is behind camera invert vector
             # If marker is behind camera invert vector
@@ -230,6 +274,10 @@ class DirectCameraControl(PandaObject):
         taskMgr.add(t, 'manipulateCamera')
         taskMgr.add(t, 'manipulateCamera')
 
 
     def mouseRotateTask(self, state):
     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 moving outside of center, ignore motion perpendicular to edge
         if ((state.constrainedDir == 'y') and (abs(direct.dr.mouseX) > 0.9)):
         if ((state.constrainedDir == 'y') and (abs(direct.dr.mouseX) > 0.9)):
             deltaX = 0
             deltaX = 0

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

@@ -54,17 +54,19 @@ class DirectManipulationControl(PandaObject):
         else:
         else:
             # Nope, off the widget, no constraint
             # Nope, off the widget, no constraint
             self.constraint = None
             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):
     def switchToMoveMode(self, state):
         taskMgr.remove('manip-watch-mouse')
         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.
         timeout is how many seconds to wait before aborting the request.
         """
         """
         assert self.notify.debugCall()
         assert self.notify.debugCall()
+        if __debug__:
+            self.__deleted=False
         assert isinstance(air, ConnectionRepository) # The api to AsyncRequest has changed.
         assert isinstance(air, ConnectionRepository) # The api to AsyncRequest has changed.
         #DirectObject.DirectObject.__init__(self)
         #DirectObject.DirectObject.__init__(self)
         self.air=air
         self.air=air
@@ -52,9 +54,12 @@ class AsyncRequest(DirectObject):
 
 
     def delete(self):
     def delete(self):
         assert self.notify.debugCall()
         assert self.notify.debugCall()
+        assert not self.__deleted
+        if __debug__:
+            self.__deleted=True
+        self.ignoreAll()
         self.timeoutTask.remove()
         self.timeoutTask.remove()
         del self.timeoutTask
         del self.timeoutTask
-        self.ignoreAll()
         if 0:
         if 0:
             for i in self.neededObjects.values():
             for i in self.neededObjects.values():
                 if i is not None:
                 if i is not None:
@@ -77,11 +82,13 @@ class AsyncRequest(DirectObject):
         call this base method to cleanup.
         call this base method to cleanup.
         """
         """
         assert self.notify.debugCall("neededObjects: %s"%(self.neededObjects,))
         assert self.notify.debugCall("neededObjects: %s"%(self.neededObjects,))
+        assert not self.__deleted
         if __debug__:
         if __debug__:
             global BreakOnTimeout
             global BreakOnTimeout
             if 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()
                 import pdb; pdb.set_trace()
         self.delete()
         self.delete()
 
 
@@ -91,6 +98,7 @@ class AsyncRequest(DirectObject):
         finish() if we do.
         finish() if we do.
         """
         """
         assert self.notify.debugCall()
         assert self.notify.debugCall()
+        assert not self.__deleted
         if name is not None:
         if name is not None:
             self.neededObjects[name]=distObj
             self.neededObjects[name]=distObj
         else:
         else:
@@ -106,6 +114,7 @@ class AsyncRequest(DirectObject):
         Request an already created object, i.e. read from database.
         Request an already created object, i.e. read from database.
         """
         """
         assert self.notify.debugCall()
         assert self.notify.debugCall()
+        assert not self.__deleted
         if key is None:
         if key is None:
             # default the dictionary key to the fieldName
             # default the dictionary key to the fieldName
             key = fieldName
             key = fieldName
@@ -128,12 +137,14 @@ class AsyncRequest(DirectObject):
         Request an already created object, i.e. read from database.
         Request an already created object, i.e. read from database.
         """
         """
         assert self.notify.debugCall()
         assert self.notify.debugCall()
+        assert not self.__deleted
         assert doId
         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:
             if context is None:
                 context=self.air.allocateContext()
                 context=self.air.allocateContext()
             self.acceptOnce(
             self.acceptOnce(
@@ -141,24 +152,6 @@ class AsyncRequest(DirectObject):
                 self._checkCompletion, [None])
                 self._checkCompletion, [None])
             self.air.queryObjectAll(doId, context)
             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):
     #def addInterestInObject(self, doId, context=None):
     #    """
     #    """
     #    Request an already created object, i.e. read from database
     #    Request an already created object, i.e. read from database
@@ -184,14 +177,28 @@ class AsyncRequest(DirectObject):
         your self.finish() function.
         your self.finish() function.
         """
         """
         assert self.notify.debugCall()
         assert self.notify.debugCall()
+        assert not self.__deleted
         assert name
         assert name
         assert className
         assert className
         self.neededObjects[name]=None
         self.neededObjects[name]=None
         if context is None:
         if context is None:
             context=self.air.allocateContext()
             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)
         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):
     def finish(self):
         """
         """
@@ -201,4 +208,5 @@ class AsyncRequest(DirectObject):
         If the other requests timeout, finish will not be called.
         If the other requests timeout, finish will not be called.
         """
         """
         assert self.notify.debugCall()
         assert self.notify.debugCall()
+        assert not self.__deleted
         self.delete()
         self.delete()

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

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

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

@@ -25,7 +25,8 @@ class ClientRepository(ConnectionRepository):
     notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository")
     notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository")
 
 
     def __init__(self):
     def __init__(self):
-        ConnectionRepository.__init__(self, base.config)
+        self.dcSuffix=""
+        ConnectionRepository.__init__(self, base.config, hasOwnerView=True)
 
 
         self.context=100000
         self.context=100000
         self.setClientDatagram(1)
         self.setClientDatagram(1)
@@ -38,6 +39,7 @@ class ClientRepository(ConnectionRepository):
 
 
         self.readDCFile()
         self.readDCFile()
         self.cache=CRCache.CRCache()
         self.cache=CRCache.CRCache()
+        self.cacheOwner=CRCache.CRCache()
         self.serverDelta = 0
         self.serverDelta = 0
 
 
         self.bootedIndex = None
         self.bootedIndex = None
@@ -76,10 +78,31 @@ class ClientRepository(ConnectionRepository):
         self.DOIDnext = 0
         self.DOIDnext = 0
         self.DOIDlast = 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
     # Define uniqueName
     def uniqueName(self, desc):
     def uniqueName(self, desc):
         return desc
         return desc
 
 
+    def getTables(self, ownerView):
+        if ownerView:
+            return self.doId2ownerView, self.cacheOwner
+        else:
+            return self.doId2do, self.cache
+
     def abruptCleanup(self):
     def abruptCleanup(self):
         """
         """
         Call this method to clean up any pending hooks or tasks on
         Call this method to clean up any pending hooks or tasks on
@@ -172,6 +195,7 @@ class ClientRepository(ConnectionRepository):
         if wantOtpServer:
         if wantOtpServer:
             parentId = di.getUint32()
             parentId = di.getUint32()
             zoneId = di.getUint32()
             zoneId = di.getUint32()
+            assert parentId == 4617 or parentId in self.doId2do
         # Get the class Id
         # Get the class Id
         classId = di.getUint16()
         classId = di.getUint16()
         # Get the DO Id
         # Get the DO Id
@@ -190,6 +214,7 @@ class ClientRepository(ConnectionRepository):
         if wantOtpServer:
         if wantOtpServer:
             parentId = di.getUint32()
             parentId = di.getUint32()
             zoneId = di.getUint32()
             zoneId = di.getUint32()
+            assert parentId == 4617 or parentId in self.doId2do
         # Get the class Id
         # Get the class Id
         classId = di.getUint16()
         classId = di.getUint16()
         # Get the DO Id
         # Get the DO Id
@@ -204,11 +229,28 @@ class ClientRepository(ConnectionRepository):
             distObj = self.generateWithRequiredOtherFields(dclass, doId, di)
             distObj = self.generateWithRequiredOtherFields(dclass, doId, di)
         dclass.stopGenerate()
         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):
     def handleQuietZoneGenerateWithRequired(self, di):
         # Special handler for quiet zone generates -- we need to filter
         # Special handler for quiet zone generates -- we need to filter
         if wantOtpServer:
         if wantOtpServer:
             parentId = di.getUint32()
             parentId = di.getUint32()
             zoneId = di.getUint32()
             zoneId = di.getUint32()
+            assert parentId in self.doId2do
         # Get the class Id
         # Get the class Id
         classId = di.getUint16()
         classId = di.getUint16()
         # Get the DO Id
         # Get the DO Id
@@ -231,6 +273,7 @@ class ClientRepository(ConnectionRepository):
         if wantOtpServer:
         if wantOtpServer:
             parentId = di.getUint32()
             parentId = di.getUint32()
             zoneId = di.getUint32()
             zoneId = di.getUint32()
+            assert parentId in self.doId2do
         # Get the class Id
         # Get the class Id
         classId = di.getUint16()
         classId = di.getUint16()
         # Get the DO Id
         # Get the DO Id
@@ -296,36 +339,36 @@ class ClientRepository(ConnectionRepository):
                 print "New DO:%s, dclass:%s"%(doId, dclass.getName())
                 print "New DO:%s, dclass:%s"%(doId, dclass.getName())
         return distObj
         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,
     def generateWithRequiredOtherFields(self, dclass, doId, di,
-            parentId = None, zoneId = None):
+                                        parentId = None, zoneId = None):
         if self.doId2do.has_key(doId):
         if self.doId2do.has_key(doId):
             # ...it is in our dictionary.
             # ...it is in our dictionary.
             # Just update it.
             # Just update it.
@@ -370,40 +413,82 @@ class ClientRepository(ConnectionRepository):
             # updateRequiredOtherFields calls announceGenerate
             # updateRequiredOtherFields calls announceGenerate
         return distObj
         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
         # Get the DO Id
         doId = di.getUint32()
         doId = di.getUint32()
         # disable it.
         # 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
             # Look up the object
-            distObj = self.doId2do[doId]
+            distObj = table[doId]
             # remove the object from the dictionary
             # remove the object from the dictionary
-            del self.doId2do[doId]
+            del table[doId]
 
 
             # Only cache the object if it is a "cacheable" type
             # Only cache the object if it is a "cacheable" type
             # object; this way we don't clutter up the caches with
             # object; this way we don't clutter up the caches with
             # trivial objects that don't benefit from caching.
             # trivial objects that don't benefit from caching.
             if distObj.getCacheable():
             if distObj.getCacheable():
-                self.cache.cache(distObj)
+                cache.cache(distObj)
             else:
             else:
                 distObj.deleteOrDelay()
                 distObj.deleteOrDelay()
         else:
         else:
             ClientRepository.notify.warning(
             ClientRepository.notify.warning(
                 "Disable failed. DistObj "
                 "Disable failed. DistObj "
                 + str(doId) +
                 + str(doId) +
-                " is not in dictionary")
+                " is not in dictionary, ownerView=%s" % ownerView)
 
 
     def handleDelete(self, di):
     def handleDelete(self, di):
+        if wantOtpServer:
+            assert 0
         # Get the DO Id
         # Get the DO Id
         doId = di.getUint32()
         doId = di.getUint32()
         self.deleteObject(doId)
         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
         Removes the object from the client's view of the world.  This
         should normally not be called except in the case of error
         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
         This is not a distributed message and does not delete the
         object on the server or on any other client.
         object on the server or on any other client.
         """
         """
+        if wantOtpServer:
+            assert 0
         if self.doId2do.has_key(doId):
         if self.doId2do.has_key(doId):
             # If it is in the dictionary, remove it.
             # If it is in the dictionary, remove it.
             obj = self.doId2do[doId]
             obj = self.doId2do[doId]
@@ -527,16 +614,17 @@ class ClientRepository(ConnectionRepository):
                 self.handleGenerateWithRequired(di)
                 self.handleGenerateWithRequired(di)
             elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER:
             elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER:
                 self.handleGenerateWithRequiredOther(di)
                 self.handleGenerateWithRequiredOther(di)
+            elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER_OWNER:
+                self.handleGenerateWithRequiredOtherOwner(di)
             elif msgType == CLIENT_OBJECT_UPDATE_FIELD:
             elif msgType == CLIENT_OBJECT_UPDATE_FIELD:
                 self.handleUpdateField(di)
                 self.handleUpdateField(di)
-            elif msgType == CLIENT_OBJECT_DISABLE_RESP:
+            elif msgType == CLIENT_OBJECT_DISABLE:
                 self.handleDisable(di)
                 self.handleDisable(di)
+            elif msgType == CLIENT_OBJECT_DISABLE_OWNER:
+                self.handleDisable(di, ownerView=True)
             elif msgType == CLIENT_OBJECT_DELETE_RESP:
             elif msgType == CLIENT_OBJECT_DELETE_RESP:
+                assert 0
                 self.handleDelete(di)
                 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:
             elif msgType == CLIENT_DONE_INTEREST_RESP:
                 self.handleInterestDoneMessage(di)
                 self.handleInterestDoneMessage(di)
             elif msgType == CLIENT_QUERY_ONE_FIELD_RESP:
             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")
     notify = DirectNotifyGlobal.directNotify.newCategory("ConnectionRepository")
     taskPriority = -30
     taskPriority = -30
 
 
-    def __init__(self, config):
+    def __init__(self, config, hasOwnerView=False):
         assert self.notify.debugCall()
         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)
         DoInterestManager.__init__(self)
         DoCollectionManager.__init__(self)
         DoCollectionManager.__init__(self)
-        CConnectionRepository.__init__(self)
         self.setPythonRepository(self)
         self.setPythonRepository(self)
 
 
         self.config = config
         self.config = config
@@ -59,9 +65,44 @@ class ConnectionRepository(
         # DC file.  The AIRepository will redefine this to 'AI'.
         # DC file.  The AIRepository will redefine this to 'AI'.
         self.dcSuffix = ''
         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
         # 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
         # Create a new distributed object, and put it in the dictionary
         #distObj = self.generateWithRequiredFields(dclass, doId, di)
         #distObj = self.generateWithRequiredFields(dclass, doId, di)
 
 
@@ -79,6 +120,8 @@ class ConnectionRepository(
         # Update the required fields
         # Update the required fields
         distObj.generateInit()  # Only called when constructed
         distObj.generateInit()  # Only called when constructed
         distObj.generate()
         distObj.generate()
+        if values is not None:
+            applyFieldValues(distObj, dclass, values)
         distObj.announceGenerate()
         distObj.announceGenerate()
         distObj.parentId = 0
         distObj.parentId = 0
         distObj.zoneId = 0
         distObj.zoneId = 0
@@ -121,7 +164,7 @@ class ConnectionRepository(
 
 
         # Now import all of the modules required by the DC file.
         # Now import all of the modules required by the DC file.
         for n in range(dcFile.getNumImportModules()):
         for n in range(dcFile.getNumImportModules()):
-            moduleName = dcFile.getImportModule(n)
+            moduleName = dcFile.getImportModule(n)[:]
 
 
             # Maybe the module name is represented as "moduleName/AI".
             # Maybe the module name is represented as "moduleName/AI".
             suffix = moduleName.split('/')
             suffix = moduleName.split('/')
@@ -184,10 +227,64 @@ class ConnectionRepository(
                 #else:
                 #else:
                 #    dclass.setClassDef(classDef)
                 #    dclass.setClassDef(classDef)
                 dclass.setClassDef(classDef)
                 dclass.setClassDef(classDef)
+
             self.dclassesByName[className] = dclass
             self.dclassesByName[className] = dclass
             if number >= 0:
             if number >= 0:
                 self.dclassesByNumber[number] = dclass
                 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):
     def importModule(self, dcImports, moduleName, importSymbols):
         """
         """
         Imports the indicated moduleName and all of its symbols
         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
         # Set the location on the server
         av.b_setLocation(self.doId, zoneId)
         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):
     def setCacheable(self, bool):
         assert((bool == 1) or (bool == 0))
         assert((bool == 1) or (bool == 0))
         self.cacheable = bool
         self.cacheable = bool
-        return None
 
 
     def getCacheable(self):
     def getCacheable(self):
         return self.cacheable
         return self.cacheable
@@ -384,6 +383,10 @@ class DistributedObject(PandaObject):
             self.cr.sendSetLocation(self.doId, parentId, zoneId)
             self.cr.sendSetLocation(self.doId, parentId, zoneId)
             
             
         def setLocation(self, 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))
             #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
             # parentId can be 'None', e.g. when an object is being disabled
             oldParentId = self.parentId
             oldParentId = self.parentId

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

@@ -182,6 +182,10 @@ class DistributedObjectAI(DirectObject):
             self.air.sendSetLocation(self, parentId, zoneId)
             self.air.sendSetLocation(self, parentId, zoneId)
 
 
         def setLocation(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
             oldParentId = self.parentId
             oldZoneId = self.zoneId
             oldZoneId = self.zoneId
             if ((oldParentId != parentId) or
             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 *
 from ClockDelta import *
 import DistributedNode
 import DistributedNode
 import DistributedSmoothNodeBase
 import DistributedSmoothNodeBase
-from direct.task import Task
+from direct.task.Task import cont
 
 
 # This number defines our tolerance for out-of-sync telemetry packets.
 # This number defines our tolerance for out-of-sync telemetry packets.
 # If a packet appears to have originated from more than MaxFuture
 # If a packet appears to have originated from more than MaxFuture
@@ -107,7 +107,7 @@ class DistributedSmoothNode(DistributedNode.DistributedNode,
             
             
     def doSmoothTask(self, task):
     def doSmoothTask(self, task):
         self.smoothPosition()
         self.smoothPosition()
-        return Task.cont
+        return cont
 
 
     def wantsSmoothing(self):
     def wantsSmoothing(self):
         # Override this function to return 0 if this particular kind
         # Override this function to return 0 if this particular kind
@@ -216,7 +216,6 @@ class DistributedSmoothNode(DistributedNode.DistributedNode,
         self.setComponentP(p)
         self.setComponentP(p)
         self.setComponentR(r)
         self.setComponentR(r)
         self.setComponentTLive(timestamp)
         self.setComponentTLive(timestamp)
-        return
 
 
     ### component set pos and hpr functions ###
     ### component set pos and hpr functions ###
 
 

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

@@ -1,5 +1,6 @@
 #hack:
 #hack:
-BAD_DO_ID = BAD_ZONE_ID = -1
+BAD_DO_ID = BAD_ZONE_ID = 0xFFFFFFFF
+BAD_CHANNEL_ID = 0xFFFFFFFFFFFFFFFF
 
 
 class DoCollectionManager:
 class DoCollectionManager:
     def __init__(self):
     def __init__(self):
@@ -8,12 +9,28 @@ class DoCollectionManager:
         # for OTP: (parentId, zoneId) to dict of doId->DistributedObjectAI
         # for OTP: (parentId, zoneId) to dict of doId->DistributedObjectAI
         # for NON-OTP: zoneId to dict of doId->DistributedObjectAI
         # for NON-OTP: zoneId to dict of doId->DistributedObjectAI
         self.zoneId2doIds={}
         self.zoneId2doIds={}
+        if self.hasOwnerView():
+            # Dict of {DistributedObject ids : DistributedObjects} for 'owner' views of objects
+            self.doId2ownerView = {}
         if wantOtpServer:
         if wantOtpServer:
             # Dict of {
             # Dict of {
             #   parent DistributedObject id: 
             #   parent DistributedObject id: 
             #     { zoneIds : [child DistributedObject ids] }}
             #     { zoneIds : [child DistributedObject ids] }}
             self.__doHierarchy = {}
             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):
     def doFind(self, str):
         """
         """
         Returns list of distributed objects with matching str in value.
         Returns list of distributed objects with matching str in value.
@@ -49,6 +66,22 @@ class DoCollectionManager:
                     distObj.dclass.getName(),
                     distObj.dclass.getName(),
                     distObj.__dict__.get("name"))
                     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):
     def getDoIdList(self, parentId, zoneId=None, classType=None):
         """
         """
         parentId is any distributed object id.
         parentId is any distributed object id.
@@ -195,24 +228,27 @@ class DoCollectionManager:
                 objList.remove(objId)
                 objList.remove(objId)
     
     
     if wantOtpServer:
     if wantOtpServer:
-        def addDOToTables(self, do, location=None):
+        def addDOToTables(self, do, location=None, ownerView=False):
             assert self.notify.debugStateCall(self)
             assert self.notify.debugStateCall(self)
             assert not hasattr(do, "isQueryAllResponse") or not do.isQueryAllResponse
             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)
                 print "ignoring repeated object %s" % (do.doId)
                 return
                 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):
         def isValidLocationTuple(self, location):
             return (location is not None
             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 pandac.PandaModules import *
 from MsgTypes import *
 from MsgTypes import *
-from direct.directnotify import DirectNotifyGlobal
 from direct.showbase.PythonUtil import *
 from direct.showbase.PythonUtil import *
 from direct.showbase import DirectObject
 from direct.showbase import DirectObject
 from PyDatagram import PyDatagram
 from PyDatagram import PyDatagram
@@ -20,7 +19,8 @@ class DoInterestManager(DirectObject.DirectObject):
     Top level Interest Manager
     Top level Interest Manager
     """
     """
     if __debug__:
     if __debug__:
-        notify = DirectNotifyGlobal.directNotify.newCategory("DoInterestManager")
+        from direct.directnotify.DirectNotifyGlobal import directNotify
+        notify = directNotify.newCategory("DoInterestManager")
 
 
     _interestIdAssign = 1
     _interestIdAssign = 1
     _interestIdScopes = 100
     _interestIdScopes = 100
@@ -201,23 +201,23 @@ class DoInterestManager(DirectObject.DirectObject):
         action based on the ID, Context
         action based on the ID, Context
         """
         """
         assert DoInterestManager.notify.debugCall()
         assert DoInterestManager.notify.debugCall()
-        id = di.getUint16()
+        interestId = di.getUint16()
         scope = di.getUint32()
         scope = di.getUint32()
-        expect_scope = self.getInterestScopeId(id)
+        expect_scope = self.getInterestScopeId(interestId)
         DoInterestManager.notify.debug(
         DoInterestManager.notify.debug(
-            "handleInterestDoneMessage--> Received ID:%s Scope:%s"%(id,scope))
+            "handleInterestDoneMessage--> Received ID:%s Scope:%s"%(interestId, scope))
         if expect_scope == scope:
         if expect_scope == scope:
             DoInterestManager.notify.debug(
             DoInterestManager.notify.debug(
                 "handleInterestDoneMessage--> Scope Match:%s Scope:%s"
                 "handleInterestDoneMessage--> Scope Match:%s Scope:%s"
-                %(id,scope))
-            event = self.getInterestScopeEvent(id)
+                %(interestId, scope))
+            event = self.getInterestScopeEvent(interestId)
             if event is not None:
             if event is not None:
                 DoInterestManager.notify.debug(
                 DoInterestManager.notify.debug(
                     "handleInterestDoneMessage--> Send Event : %s"%(event))
                     "handleInterestDoneMessage--> Send Event : %s"%(event))
                 messenger.send(event)
                 messenger.send(event)
             else:
             else:
                 DoInterestManager.notify.debug("handleInterestDoneMessage--> No Event ")
                 DoInterestManager.notify.debug("handleInterestDoneMessage--> No Event ")
-            self._ponderRemoveFlaggedInterest(id)
+            self._ponderRemoveFlaggedInterest(interestId)
         else:
         else:
             DoInterestManager.notify.debug(
             DoInterestManager.notify.debug(
                 "handleInterestDoneMessage--> Scope MisMatch :%s :%s"
                 "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_UPDATE_FIELD_RESP =            24
 CLIENT_OBJECT_DISABLE =                      25
 CLIENT_OBJECT_DISABLE =                      25
 CLIENT_OBJECT_DISABLE_RESP =                 25
 CLIENT_OBJECT_DISABLE_RESP =                 25
+CLIENT_OBJECT_DISABLE_OWNER =                26
+CLIENT_OBJECT_DISABLE_OWNER_RESP =           26
 CLIENT_OBJECT_DELETE =                       27
 CLIENT_OBJECT_DELETE =                       27
 CLIENT_OBJECT_DELETE_RESP =                  27
 CLIENT_OBJECT_DELETE_RESP =                  27
 if not wantOtpServer:        
 if not wantOtpServer:        
@@ -37,6 +39,8 @@ CLIENT_CREATE_OBJECT_REQUIRED =              34
 CLIENT_CREATE_OBJECT_REQUIRED_RESP =         34
 CLIENT_CREATE_OBJECT_REQUIRED_RESP =         34
 CLIENT_CREATE_OBJECT_REQUIRED_OTHER =        35
 CLIENT_CREATE_OBJECT_REQUIRED_OTHER =        35
 CLIENT_CREATE_OBJECT_REQUIRED_OTHER_RESP =   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
 CLIENT_REQUEST_GENERATES =                   36
 
 

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

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

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

@@ -28,6 +28,17 @@ get_dc_file() {
   return _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
 //     Function: CConnectionRepository::set_client_datagram
 //       Access: Published
 //       Access: Published

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

@@ -41,7 +41,7 @@ PStatCollector CConnectionRepository::_update_pcollector("App:Show code:readerPo
 //  Description: 
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 CConnectionRepository::
 CConnectionRepository::
-CConnectionRepository() :
+CConnectionRepository(bool has_owner_view) :
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
   _python_repository(NULL),
   _python_repository(NULL),
 #endif
 #endif
@@ -57,7 +57,8 @@ CConnectionRepository() :
   _verbose(distributed_cat.is_spam()),
   _verbose(distributed_cat.is_spam()),
 //  _msg_channels(),
 //  _msg_channels(),
   _msg_sender(0),
   _msg_sender(0),
-  _msg_type(0)
+  _msg_type(0),
+  _has_owner_view(has_owner_view)
 {
 {
 #if defined(HAVE_NSPR) && defined(SIMULATE_NETWORK_DELAY)
 #if defined(HAVE_NSPR) && defined(SIMULATE_NETWORK_DELAY)
   if (min_lag != 0.0 || max_lag != 0.0) {
   if (min_lag != 0.0 || max_lag != 0.0) {
@@ -246,8 +247,14 @@ check_datagram() {
 #ifdef HAVE_PYTHON
 #ifdef HAVE_PYTHON
     case CLIENT_OBJECT_UPDATE_FIELD:
     case CLIENT_OBJECT_UPDATE_FIELD:
     case STATESERVER_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;
       break;
 #endif  // HAVE_PYTHON
 #endif  // HAVE_PYTHON
@@ -514,6 +521,118 @@ handle_update_field() {
         return false;
         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  
   #endif  // HAVE_PYTHON  
 
 

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

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

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

@@ -322,7 +322,6 @@ class DirectScrolledList(DirectFrame):
             self.refresh()
             self.refresh()
         if(type(item) == types.InstanceType):
         if(type(item) == types.InstanceType):
             return item.itemID  # to pass to scrollToItemID
             return item.itemID  # to pass to scrollToItemID
-        return
 
 
     def removeItem(self, item, refresh=1):
     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_callees(lines)
             s.print_callers(lines)
             s.print_callers(lines)
 
 
-def capitalizeFirst(str):
-    return '%s%s' % (string.upper(str[0]), str[1:])
-
 def getSetterName(valueName, prefix='set'):
 def getSetterName(valueName, prefix='set'):
     # getSetterName('color') -> 'setColor'
     # getSetterName('color') -> 'setColor'
     # getSetterName('color', 'get') -> 'getColor'
     # getSetterName('color', 'get') -> 'getColor'

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

@@ -12,7 +12,8 @@ class Transitions:
     FadeModelName = "models/misc/fade"
     FadeModelName = "models/misc/fade"
 
 
     def __init__(self, loader):
     def __init__(self, loader):
-        self.ival = None
+        self.transitionIval = None
+        self.letterboxIval = None
         self.iris = None
         self.iris = None
         self.fade = None
         self.fade = None
         self.letterbox = None
         self.letterbox = None
@@ -60,15 +61,15 @@ class Transitions:
             # Create a sequence that lerps the color out, then
             # Create a sequence that lerps the color out, then
             # parents the fade to hidden
             # parents the fade to hidden
             self.fade.reparentTo(aspect2d, FADE_SORT_INDEX)
             self.fade.reparentTo(aspect2d, FADE_SORT_INDEX)
-            self.ival = Sequence(LerpColorInterval(self.fade, t,
+            self.transitionIval = Sequence(LerpColorInterval(self.fade, t,
                                                    color = self.alphaOff,
                                                    color = self.alphaOff,
                                                    startColor = self.alphaOn),
                                                    startColor = self.alphaOn),
                                  Func(self.fade.detachNode),
                                  Func(self.fade.detachNode),
                                  name = self.fadeTaskName,
                                  name = self.fadeTaskName,
                                  )
                                  )
             if finishIval:
             if finishIval:
-                self.ival.append(finishIval)
-            self.ival.start()
+                self.transitionIval.append(finishIval)
+            self.transitionIval.start()
             
             
     def fadeOut(self, t=0.5, finishIval=None):
     def fadeOut(self, t=0.5, finishIval=None):
         """
         """
@@ -88,14 +89,17 @@ class Transitions:
         else:
         else:
             # Create a sequence that lerps the color out, then
             # Create a sequence that lerps the color out, then
             # parents the fade to hidden
             # parents the fade to hidden
-            self.ival = Sequence(LerpColorInterval(self.fade, t,
+            self.transitionIval = Sequence(LerpColorInterval(self.fade, t,
                                                    color = self.alphaOn,
                                                    color = self.alphaOn,
                                                    startColor = self.alphaOff),
                                                    startColor = self.alphaOff),
                                  name = self.fadeTaskName,
                                  name = self.fadeTaskName,
                                  )
                                  )
             if finishIval:
             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):
     def fadeScreen(self, alpha=0.5):
         """
         """
@@ -126,9 +130,9 @@ class Transitions:
         """
         """
         Removes any current fade tasks and parents the fade polygon away
         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:
         if self.fade:
             self.fade.detachNode()
             self.fade.detachNode()
 
 
@@ -160,15 +164,15 @@ class Transitions:
         else:
         else:
             self.iris.reparentTo(aspect2d, FADE_SORT_INDEX)
             self.iris.reparentTo(aspect2d, FADE_SORT_INDEX)
 
 
-            self.ival = Sequence(LerpScaleInterval(self.iris, t,
+            self.transitionIval = Sequence(LerpScaleInterval(self.iris, t,
                                                    scale = 0.18,
                                                    scale = 0.18,
                                                    startScale = 0.01),
                                                    startScale = 0.01),
                                  Func(self.iris.detachNode),
                                  Func(self.iris.detachNode),
                                  name = self.irisTaskName,
                                  name = self.irisTaskName,
                                  )
                                  )
             if finishIval:
             if finishIval:
-                self.ival.append(finishIval)
-            self.ival.start()
+                self.transitionIval.append(finishIval)
+            self.transitionIval.start()
             
             
     def irisOut(self, t=0.5, finishIval=None):
     def irisOut(self, t=0.5, finishIval=None):
         """
         """
@@ -187,7 +191,7 @@ class Transitions:
         else:
         else:
             self.iris.reparentTo(aspect2d, FADE_SORT_INDEX)
             self.iris.reparentTo(aspect2d, FADE_SORT_INDEX)
 
 
-            self.ival = Sequence(LerpScaleInterval(self.iris, t,
+            self.transitionIval = Sequence(LerpScaleInterval(self.iris, t,
                                                    scale = 0.01,
                                                    scale = 0.01,
                                                    startScale = 0.18),
                                                    startScale = 0.18),
                                  Func(self.iris.detachNode),
                                  Func(self.iris.detachNode),
@@ -196,16 +200,16 @@ class Transitions:
                                  name = self.irisTaskName,
                                  name = self.irisTaskName,
                                  )
                                  )
             if finishIval:
             if finishIval:
-                self.ival.append(finishIval)
-            self.ival.start()
+                self.transitionIval.append(finishIval)
+            self.transitionIval.start()
 
 
     def noIris(self):
     def noIris(self):
         """
         """
         Removes any current iris tasks and parents the iris polygon away
         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:
         if self.iris != None:
             self.iris.detachNode()
             self.iris.detachNode()
         # Actually we need to remove the fade too,
         # Actually we need to remove the fade too,
@@ -218,7 +222,8 @@ class Transitions:
         """
         """
         self.noFade()
         self.noFade()
         self.noIris()
         self.noIris()
-        self.noLetterbox()
+        # Letterbox is not really a transition, it is a screen overlay
+        # self.noLetterbox()
 
 
     ##################################################
     ##################################################
     # Letterbox
     # Letterbox
@@ -258,9 +263,9 @@ class Transitions:
         """
         """
         Removes any current letterbox tasks and parents the letterbox polygon away
         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:
         if self.letterbox != None:
             self.letterbox.detachNode()
             self.letterbox.detachNode()
 
 
@@ -268,7 +273,7 @@ class Transitions:
         """
         """
         Move black bars in over t seconds.
         Move black bars in over t seconds.
         """
         """
-        self.noTransitions()
+        self.noLetterbox()
         self.loadLetterbox()
         self.loadLetterbox()
         if (t == 0):
         if (t == 0):
             self.letterbox.reparentTo(render2d, FADE_SORT_INDEX)
             self.letterbox.reparentTo(render2d, FADE_SORT_INDEX)
@@ -276,7 +281,7 @@ class Transitions:
             self.letterboxTop.setPos(0,0,0.8)
             self.letterboxTop.setPos(0,0,0.8)
         else:
         else:
             self.letterbox.reparentTo(render2d, FADE_SORT_INDEX)
             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),
                                                           pos = Vec3(0,0,-1),
                                                           startPos = Vec3(0,0,-1.2)),
                                                           startPos = Vec3(0,0,-1.2)),
                                           LerpPosInterval(self.letterboxTop, t,
                                           LerpPosInterval(self.letterboxTop, t,
@@ -289,20 +294,20 @@ class Transitions:
                                  name = self.letterboxTaskName,
                                  name = self.letterboxTaskName,
                                  )
                                  )
             if finishIval:
             if finishIval:
-                self.ival.append(finishIval)
-            self.ival.start()
+                self.letterboxIval.append(finishIval)
+            self.letterboxIval.start()
             
             
     def letterboxOff(self, t=0.25, finishIval=None):
     def letterboxOff(self, t=0.25, finishIval=None):
         """
         """
         Move black bars away over t seconds.
         Move black bars away over t seconds.
         """
         """
-        self.noTransitions()
+        self.noLetterbox()
         self.loadLetterbox()
         self.loadLetterbox()
         if (t == 0):
         if (t == 0):
             self.letterbox.detachNode()
             self.letterbox.detachNode()
         else:
         else:
             self.letterbox.reparentTo(render2d, FADE_SORT_INDEX)
             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),
                                                           pos = Vec3(0,0,-1.2),
                                                           startPos = Vec3(0,0,-1)),
                                                           startPos = Vec3(0,0,-1)),
                                           LerpPosInterval(self.letterboxTop, t,
                                           LerpPosInterval(self.letterboxTop, t,
@@ -315,5 +320,5 @@ class Transitions:
                                  name = self.letterboxTaskName,
                                  name = self.letterboxTaskName,
                                  )
                                  )
             if finishIval:
             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)
         signal.signal(signal.SIGINT, signal.default_int_handler)
         if self.fKeyboardInterrupt:
         if self.fKeyboardInterrupt:
             raise KeyboardInterrupt
             raise KeyboardInterrupt
-        return
 
 
     def run(self):
     def run(self):
         # Set the clock to have last frame's time in case we were
         # Set the clock to have last frame's time in case we were
@@ -783,6 +782,17 @@ class TaskManager:
                     self.step()
                     self.step()
                 except KeyboardInterrupt:
                 except KeyboardInterrupt:
                     self.stop()
                     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:
                 except:
                     if self.extendedExceptions:
                     if self.extendedExceptions:
                         self.stop()
                         self.stop()

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

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