Browse Source

better PandaNode::get_children(), introduce PandaNode::reset_all_prev_transform()

David Rose 19 years ago
parent
commit
160c848b68

+ 4 - 25
panda/src/collide/collisionTraverser.cxx

@@ -41,7 +41,6 @@
 #include "indent.h"
 
 PStatCollector CollisionTraverser::_collisions_pcollector("App:Collisions");
-PStatCollector CollisionTraverser::_reset_prev_pcollector("App:Collisions:Reset");
 
 PStatCollector CollisionTraverser::_cnode_volume_pcollector("Collision Volumes:CollisionNode");
 PStatCollector CollisionTraverser::_gnode_volume_pcollector("Collision Volumes:GeomNode");
@@ -313,16 +312,12 @@ traverse(const NodePath &root) {
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionTraverser::reset_prev_transform
 //       Access: Published
-//  Description: Once the collision traversal has finished, resets all
-//               of the velocity deltas in the scene graph by setting
-//               the "previous" transform to the current transform.
-//               This must be called at least once per frame for
-//               collisions to respect this velocity setting properly.
+//  Description: This method is deprecated.  Just call
+//               PandaNode::reset_all_prev_transform() now.
 ////////////////////////////////////////////////////////////////////
 void CollisionTraverser::
-reset_prev_transform(const NodePath &root) {
-  PStatTimer timer(_reset_prev_pcollector);
-  r_reset_prev_transform(root.node());
+reset_prev_transform(const NodePath &) {
+  PandaNode::reset_all_prev_transform();
 }
 
 #ifdef DO_COLLISION_RECORDING
@@ -883,22 +878,6 @@ remove_handler(CollisionTraverser::Handlers::iterator hi) {
   return hi;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: CollisionTraverser::r_reset_prev_transform
-//       Access: Private
-//  Description: 
-////////////////////////////////////////////////////////////////////
-void CollisionTraverser::
-r_reset_prev_transform(PandaNode *node) {
-  node->reset_prev_transform();
-
-  PandaNode::Children children = node->get_children();
-  int num_children = children.get_num_children();
-  for (int i = 0; i < num_children; ++i) {
-    r_reset_prev_transform(children.get_child(i));
-  }
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionTraverser::get_pass_collector
 //       Access: Private

+ 0 - 3
panda/src/collide/collisionTraverser.h

@@ -102,8 +102,6 @@ private:
                                 const GeometricBoundingVolume *from_node_gbv,
                                 const GeometricBoundingVolume *solid_gbv);
 
-  void r_reset_prev_transform(PandaNode *node);
-
   PStatCollector &get_pass_collector(int pass);
 
 private:
@@ -134,7 +132,6 @@ private:
 
   // Statistics
   static PStatCollector _collisions_pcollector;
-  static PStatCollector _reset_prev_pcollector;
 
   static PStatCollector _cnode_volume_pcollector;
   static PStatCollector _gnode_volume_pcollector;

+ 3 - 0
panda/src/pgraph/Sources.pp

@@ -73,6 +73,7 @@
     nodePathComponent.I nodePathComponent.h \
     nodePathLerps.h \
     pandaNode.I pandaNode.h \
+    pandaNodeChain.I pandaNodeChain.h \
     planeNode.I planeNode.h \
     pointLight.I pointLight.h \
     polylightNode.I polylightNode.h \
@@ -174,6 +175,7 @@
     nodePathComponent.cxx \
     nodePathLerps.cxx \
     pandaNode.cxx \
+    pandaNodeChain.cxx \
     planeNode.cxx \
     pointLight.cxx \
     polylightNode.cxx \
@@ -272,6 +274,7 @@
     nodePathComponent.I nodePathComponent.h \
     nodePathLerps.h \
     pandaNode.I pandaNode.h \
+    pandaNodeChain.I pandaNodeChain.h \
     planeNode.I planeNode.h \
     pointLight.I pointLight.h \
     polylightNode.I polylightNode.h \

+ 5 - 7
panda/src/pgraph/cullTraverser.cxx

@@ -248,22 +248,20 @@ traverse_below(CullTraverserData &data) {
 #endif
     }
 
-    // Now visit all the node's children.  We cannot use the
-    // node->get_children() interface, because that will keep a read
-    // pointer open, and we might end up changing this node during the
-    // traversal.
-    int num_children = node->get_num_children();
+    // Now visit all the node's children.
+    PandaNode::Children children = node->get_children();
+    int num_children = children.get_num_children();
     if (node->has_selective_visibility()) {
       int i = node->get_first_visible_child();
       while (i < num_children) {
-        CullTraverserData next_data(data, node->get_child(i));
+        CullTraverserData next_data(data, children.get_child(i));
         traverse(next_data);
         i = node->get_next_visible_child(i);
       }
       
     } else {
       for (int i = 0; i < num_children; i++) {
-        CullTraverserData next_data(data, node->get_child(i));
+        CullTraverserData next_data(data, children.get_child(i));
         traverse(next_data);
       }
     }

+ 13 - 9
panda/src/pgraph/geomNode.cxx

@@ -191,8 +191,9 @@ apply_attribs_to_vertices(const AccumulatedAttribs &attribs, int attrib_types,
     }
   }
 
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler) {
-    CDStageWriter cdata(_cycler, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
+    CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
     Geoms::iterator gi;
     for (gi = cdata->_geoms.begin(); gi != cdata->_geoms.end(); ++gi) {
       GeomEntry &entry = (*gi);
@@ -409,8 +410,9 @@ add_geom(Geom *geom, const RenderState *state) {
   nassertv(geom->check_valid());
   nassertv(state != (RenderState *)NULL);
 
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler) {
-    CDStageWriter cdata(_cycler, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
+    CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
 
     cdata->_geoms.push_back(GeomEntry(geom, state));
   }
@@ -427,9 +429,10 @@ add_geom(Geom *geom, const RenderState *state) {
 ////////////////////////////////////////////////////////////////////
 void GeomNode::
 add_geoms_from(const GeomNode *other) {
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler) {
-    CDStageWriter cdata(_cycler, pipeline_stage);
-    CDStageReader cdata_other(other->_cycler, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
+    CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
+    CDStageReader cdata_other(other->_cycler, pipeline_stage, current_thread);
 
     Geoms::const_iterator gi;
     for (gi = cdata_other->_geoms.begin(); 
@@ -511,8 +514,9 @@ check_valid() const {
 ////////////////////////////////////////////////////////////////////
 void GeomNode::
 unify() {
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler) {
-    CDStageWriter cdata(_cycler, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler, current_thread) {
+    CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
 
     Geoms new_geoms;
 

+ 3 - 2
panda/src/pgraph/geomTransformer.cxx

@@ -128,8 +128,9 @@ bool GeomTransformer::
 transform_vertices(GeomNode *node, const LMatrix4f &mat) {
   bool any_changed = false;
 
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(node->_cycler) {
-    GeomNode::CDStageWriter cdata(node->_cycler, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(node->_cycler, current_thread) {
+    GeomNode::CDStageWriter cdata(node->_cycler, pipeline_stage, current_thread);
     GeomNode::Geoms::iterator gi;
     for (gi = cdata->_geoms.begin(); gi != cdata->_geoms.end(); ++gi) {
       GeomNode::GeomEntry &entry = (*gi);

+ 213 - 203
panda/src/pgraph/pandaNode.I

@@ -86,20 +86,23 @@ compare_draw_mask(DrawMask running_draw_mask, DrawMask camera_mask) const {
 INLINE int PandaNode::
 get_num_parents() const {
   CDLinksReader cdata(_cycler_links);
-  return cdata->_up.size();
+  return cdata->get_up()->size();
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::get_parent
 //       Access: Published
 //  Description: Returns the nth parent node of this node.  See
-//               get_num_parents().
+//               get_num_parents().  Also see get_parents(), if your
+//               intention is to iterate through the complete list of
+//               parents; get_parents() is preferable in this case.
 ////////////////////////////////////////////////////////////////////
 INLINE PandaNode *PandaNode::
 get_parent(int n) const {
   CDLinksReader cdata(_cycler_links);
-  nassertr(n >= 0 && n < (int)cdata->_up.size(), NULL);
-  return cdata->_up[n].get_parent();
+  const Up &up = *cdata->get_up();
+  nassertr(n >= 0 && n < (int)up.size(), NULL);
+  return up[n].get_parent();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -125,20 +128,23 @@ find_parent(PandaNode *node) const {
 INLINE int PandaNode::
 get_num_children() const {
   CDLinksReader cdata(_cycler_links);
-  return cdata->_down.size();
+  return cdata->get_down()->size();
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::get_child
 //       Access: Published
 //  Description: Returns the nth child node of this node.  See
-//               get_num_children().
+//               get_num_children().  Also see get_children(), if your
+//               intention is to iterate through the complete list of
+//               children; get_children() is preferable in this case.
 ////////////////////////////////////////////////////////////////////
 INLINE PandaNode *PandaNode::
 get_child(int n) const {
   CDLinksReader cdata(_cycler_links);
-  nassertr(n >= 0 && n < (int)cdata->_down.size(), NULL);
-  return cdata->_down[n].get_child();
+  const Down &down = *cdata->get_down();
+  nassertr(n >= 0 && n < (int)down.size(), NULL);
+  return down[n].get_child();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -151,8 +157,9 @@ get_child(int n) const {
 INLINE int PandaNode::
 get_child_sort(int n) const {
   CDLinksReader cdata(_cycler_links);
-  nassertr(n >= 0 && n < (int)cdata->_down.size(), -1);
-  return cdata->_down[n].get_sort();
+  const Down &down = *cdata->get_down();
+  nassertr(n >= 0 && n < (int)down.size(), -1);
+  return down[n].get_sort();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -229,20 +236,24 @@ unstash_child(PandaNode *child_node) {
 INLINE int PandaNode::
 get_num_stashed() const {
   CDLinksReader cdata(_cycler_links);
-  return cdata->_stashed.size();
+  return cdata->get_stashed()->size();
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::get_stashed
 //       Access: Published
-//  Description: Returns the nth stashed node of this node.  See
-//               get_num_stashed().
+//  Description: Returns the nth stashed child of this node.  See
+//               get_num_stashed().  Also see get_stashed(), if your
+//               intention is to iterate through the complete list of
+//               stashed children; get_stashed() is preferable in this
+//               case.
 ////////////////////////////////////////////////////////////////////
 INLINE PandaNode *PandaNode::
 get_stashed(int n) const {
   CDLinksReader cdata(_cycler_links);
-  nassertr(n >= 0 && n < (int)cdata->_stashed.size(), NULL);
-  return cdata->_stashed[n].get_child();
+  const Down &stashed = *cdata->get_stashed();
+  nassertr(n >= 0 && n < (int)stashed.size(), NULL);
+  return stashed[n].get_child();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -255,8 +266,9 @@ get_stashed(int n) const {
 INLINE int PandaNode::
 get_stashed_sort(int n) const {
   CDLinksReader cdata(_cycler_links);
-  nassertr(n >= 0 && n < (int)cdata->_stashed.size(), -1);
-  return cdata->_stashed[n].get_sort();
+  const Down &stashed = *cdata->get_stashed();
+  nassertr(n >= 0 && n < (int)stashed.size(), -1);
+  return stashed[n].get_sort();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -422,6 +434,20 @@ get_prev_transform() const {
   return cdata->_prev_transform;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::has_dirty_prev_transform
+//       Access: Published
+//  Description: Returns true if this node has the
+//               _dirty_prev_transform flag set, which indicates its
+//               _prev_transform is different from its _transform
+//               value (in pipeline stage 0).  In this case, the node
+//               will be visited by reset_prev_transform().
+////////////////////////////////////////////////////////////////////
+INLINE bool PandaNode::
+has_dirty_prev_transform() const {
+  return _dirty_prev_transform;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::get_tag
 //       Access: Published
@@ -690,22 +716,6 @@ mark_internal_bounds_stale(int pipeline_stage) {
   mark_bounds_stale(pipeline_stage);
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::get_children_copy
-//       Access: Public
-//  Description: Returns an object that can be used to walk through
-//               the list of children of the node.  Unlike
-//               get_children(), this function actually returns an
-//               object that protects you from self-modifying loops,
-//               because it makes and returns a copy of the complete
-//               children list.
-////////////////////////////////////////////////////////////////////
-INLINE PandaNode::ChildrenCopy PandaNode::
-get_children_copy() const {
-  CDLinksReader cdata(_cycler_links);
-  return ChildrenCopy(cdata);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::get_children
 //       Access: Public
@@ -713,74 +723,56 @@ get_children_copy() const {
 //               the list of children of the node.  When you intend to
 //               visit multiple children, using this is slightly
 //               faster than calling get_child() directly on the
-//               PandaNode, since this object keeps the PipelineCycler
-//               open the whole time.
+//               PandaNode, since this object avoids reopening the
+//               PipelineCycler each time.
 //
-//               However, this object does not protect you from
-//               self-modifying loops (e.g. adding or removing
-//               children during traversal).  Furthermore, if Panda is
-//               compiled with pipelining enabled, this method is
-//               compiled to behave exactly as get_children_copy(): it
-//               returns a copy.  This is intended to reduce the risk
-//               of deadlocks.
+//               This object also protects you from self-modifying
+//               loops (e.g. adding or removing children during
+//               traversal), since a virtual copy of the children is
+//               made ahead of time.  The virtual copy is fast--it is
+//               a form of copy-on-write, so the list is not actually
+//               copied unless it is modified during the traversal.
 ////////////////////////////////////////////////////////////////////
 INLINE PandaNode::Children PandaNode::
-get_children() const {
-  CDLinksReader cdata(_cycler_links);
+get_children(Thread *current_thread) const {
+  CDLinksReader cdata(_cycler_links, current_thread);
   return Children(cdata);
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::get_stashed_copy
-//       Access: Public
-//  Description: Returns an object that can be used to walk through
-//               the list of stashed children of the node.  Unlike
-//               get_stashed(), this function actually returns an
-//               object that protects you from self-modifying loops,
-//               because it makes and returns a copy of the complete
-//               stashed list.
-////////////////////////////////////////////////////////////////////
-INLINE PandaNode::StashedCopy PandaNode::
-get_stashed_copy() const {
-  CDLinksReader cdata(_cycler_links);
-  return StashedCopy(cdata);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::get_stashed
 //       Access: Public
 //  Description: Returns an object that can be used to walk through
-//               the list of stashed children of the node.  When you
-//               intend to visit multiple children, using this is
-//               slightly faster than calling get_stashed() directly
-//               on the PandaNode, since this object keeps the
-//               PipelineCycler open the whole time.
+//               the list of children of the node.  When you intend to
+//               visit multiple children, using this is slightly
+//               faster than calling get_stashed() directly on the
+//               PandaNode, since this object avoids reopening the
+//               PipelineCycler each time.
 //
-//               However, this object does not protect you from
-//               self-modifying loops (e.g. adding or removing
-//               stashed during traversal).  Furthermore, if Panda is
-//               compiled with pipelining enabled, this method is
-//               compiled to behave exactly as get_stashed_copy(): it
-//               returns a copy.  This is intended to reduce the risk
-//               of deadlocks.
+//               This object also protects you from self-modifying
+//               loops (e.g. adding or removing children during
+//               traversal), since a virtual copy of the children is
+//               made ahead of time.  The virtual copy is fast--it is
+//               a form of copy-on-write, so the list is not actually
+//               copied unless it is modified during the traversal.
 ////////////////////////////////////////////////////////////////////
 INLINE PandaNode::Stashed PandaNode::
-get_stashed() const {
-  CDLinksReader cdata(_cycler_links);
+get_stashed(Thread *current_thread) const {
+  CDLinksReader cdata(_cycler_links, current_thread);
   return Stashed(cdata);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::get_parents_copy
+//     Function: PandaNode::get_parents
 //       Access: Public
 //  Description: Returns an object that can be used to walk through
 //               the list of parents of the node, similar to
-//               get_children_copy()/
+//               get_children() and get_stashed().
 ////////////////////////////////////////////////////////////////////
-INLINE PandaNode::ParentsCopy PandaNode::
-get_parents_copy() const {
-  CDLinksReader cdata(_cycler_links);
-  return ParentsCopy(cdata);
+INLINE PandaNode::Parents PandaNode::
+get_parents(Thread *current_thread) const {
+  CDLinksReader cdata(_cycler_links, current_thread);
+  return Parents(cdata);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -790,11 +782,46 @@ get_parents_copy() const {
 ////////////////////////////////////////////////////////////////////
 INLINE int PandaNode::
 do_find_parent(PandaNode *node, const CDataLinks *cdata) const {
-  Up::const_iterator ui = cdata->_up.find(UpConnection(node));
-  if (ui == cdata->_up.end()) {
+  const Up &up = *cdata->get_up();
+  Up::const_iterator ui = up.find(UpConnection(node));
+  if (ui == up.end()) {
     return -1;
   }
-  return ui - cdata->_up.begin();
+  return ui - up.begin();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::set_dirty_prev_transform
+//       Access: Private
+//  Description: Sets the dirty_prev_transform flag, and adds the node
+//               to the _dirty_prev_transforms chain.
+////////////////////////////////////////////////////////////////////
+INLINE void PandaNode::
+set_dirty_prev_transform() {
+  if (!_dirty_prev_transform) {
+    MutexHolder holder(_dirty_prev_transforms._lock);
+    if (!_dirty_prev_transform) {
+      LinkedListNode::insert_before(&_dirty_prev_transforms);
+      _dirty_prev_transform = true;
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::clear_dirty_prev_transform
+//       Access: Private
+//  Description: Clears the dirty_prev_transform flag, and removes the node
+//               from the _dirty_prev_transforms chain.
+////////////////////////////////////////////////////////////////////
+INLINE void PandaNode::
+clear_dirty_prev_transform() {
+  if (_dirty_prev_transform) {
+    MutexHolder holder(_dirty_prev_transforms._lock);
+    if (_dirty_prev_transform) {
+      LinkedListNode::remove_from_list();
+      _dirty_prev_transform = false;
+    }
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -936,115 +963,138 @@ CDataBounds() {
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE PandaNode::CDataLinks::
-CDataLinks() {
+CDataLinks() : 
+  _down(new PandaNode::Down),
+  _stashed(new PandaNode::Down),
+  _up(new PandaNode::Up)
+{
 }
 
-#ifndef DO_PIPELINING
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::Children::Constructor
+//     Function: PandaNode::CDataLinks::get_down
 //       Access: Public
-//  Description:
+//  Description: Returns a read-only pointer to the _down list.
 ////////////////////////////////////////////////////////////////////
-INLINE PandaNode::Children::
-Children(const PandaNode::CDLinksReader &cdata) :
-  _cdata(cdata)
-{
+INLINE const PandaNode::Down *PandaNode::CDataLinks::
+get_down() const {
+  return _down;
 }
-#endif  // DO_PIPELINING
 
-#ifndef DO_PIPELINING
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::Children::Copy Constructor
+//     Function: PandaNode::CDataLinks::modify_down
 //       Access: Public
-//  Description:
+//  Description: Returns a modifiable, unique pointer to the _down
+//               list.
 ////////////////////////////////////////////////////////////////////
-INLINE PandaNode::Children::
-Children(const PandaNode::Children &copy) :
-  _cdata(copy._cdata)
-{
+INLINE PandaNode::Down *PandaNode::CDataLinks::
+modify_down() {
+  if (_down->get_ref_count() > 1) {
+    _down = new Down(*_down);
+  }
+  return _down;
 }
-#endif  // DO_PIPELINING
 
-#ifndef DO_PIPELINING
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::Children::Copy Assignment Operator
+//     Function: PandaNode::CDataLinks::get_stashed
 //       Access: Public
-//  Description:
+//  Description: Returns a read-only pointer to the _stashed list.
 ////////////////////////////////////////////////////////////////////
-INLINE void PandaNode::Children::
-operator = (const PandaNode::Children &copy) {
-  _cdata = copy._cdata;
+INLINE const PandaNode::Down *PandaNode::CDataLinks::
+get_stashed() const {
+  return _stashed;
 }
-#endif  // DO_PIPELINING
 
-#ifndef DO_PIPELINING
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::Children::get_num_children
+//     Function: PandaNode::CDataLinks::modify_stashed
 //       Access: Public
-//  Description: Returns the number of children of the node.
+//  Description: Returns a modifiable, unique pointer to the _stashed
+//               list.
 ////////////////////////////////////////////////////////////////////
-INLINE int PandaNode::Children::
-get_num_children() const {
-  return _cdata->_down.size();
+INLINE PandaNode::Down *PandaNode::CDataLinks::
+modify_stashed() {
+  if (_stashed->get_ref_count() > 1) {
+    _stashed = new Down(*_stashed);
+  }
+  return _stashed;
 }
-#endif  // DO_PIPELINING
 
-#ifndef DO_PIPELINING
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::Children::get_child
+//     Function: PandaNode::CDataLinks::get_up
 //       Access: Public
-//  Description: Returns the nth child of the node.
+//  Description: Returns a read-only pointer to the _up list.
 ////////////////////////////////////////////////////////////////////
-INLINE PandaNode *PandaNode::Children::
-get_child(int n) const {
-  nassertr(n >= 0 && n < (int)_cdata->_down.size(), NULL);
-  return _cdata->_down[n].get_child();
+INLINE const PandaNode::Up *PandaNode::CDataLinks::
+get_up() const {
+  return _up;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::CDataLinks::modify_up
+//       Access: Public
+//  Description: Returns a modifiable, unique pointer to the _up
+//               list.
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode::Up *PandaNode::CDataLinks::
+modify_up() {
+  if (_up->get_ref_count() > 1) {
+    _up = new Up(*_up);
+  }
+  return _up;
 }
-#endif  // DO_PIPELINING
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::ChildrenCopy::Copy Constructor
+//     Function: PandaNode::Children::Constructor
 //       Access: Public
 //  Description:
 ////////////////////////////////////////////////////////////////////
-INLINE PandaNode::ChildrenCopy::
-ChildrenCopy(const PandaNode::ChildrenCopy &copy) :
-  _list(copy._list)
+INLINE PandaNode::Children::
+Children(const PandaNode::CDLinksReader &cdata) :
+  _down(cdata->get_down())
 {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::ChildrenCopy::Copy Assignment Operator
+//     Function: PandaNode::Children::Copy Constructor
 //       Access: Public
 //  Description:
 ////////////////////////////////////////////////////////////////////
-INLINE void PandaNode::ChildrenCopy::
-operator = (const PandaNode::ChildrenCopy &copy) {
-  _list = copy._list;
+INLINE PandaNode::Children::
+Children(const PandaNode::Children &copy) :
+  _down(copy._down)
+{
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::ChildrenCopy::get_num_children
+//     Function: PandaNode::Children::Copy Assignment Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void PandaNode::Children::
+operator = (const PandaNode::Children &copy) {
+  _down = copy._down;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::Children::get_num_children
 //       Access: Public
 //  Description: Returns the number of children of the node.
 ////////////////////////////////////////////////////////////////////
-INLINE int PandaNode::ChildrenCopy::
+INLINE int PandaNode::Children::
 get_num_children() const {
-  return _list.size();
+  return _down->size();
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::ChildrenCopy::get_child
+//     Function: PandaNode::Children::get_child
 //       Access: Public
 //  Description: Returns the nth child of the node.
 ////////////////////////////////////////////////////////////////////
-INLINE PandaNode *PandaNode::ChildrenCopy::
+INLINE PandaNode *PandaNode::Children::
 get_child(int n) const {
-  nassertr(n >= 0 && n < (int)_list.size(), NULL);
-  return _list[n];
+  nassertr(n >= 0 && n < (int)_down->size(), NULL);
+  return (*_down)[n].get_child();
 }
 
-#ifndef DO_PIPELINING
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::Stashed::Constructor
 //       Access: Public
@@ -1052,12 +1102,10 @@ get_child(int n) const {
 ////////////////////////////////////////////////////////////////////
 INLINE PandaNode::Stashed::
 Stashed(const PandaNode::CDLinksReader &cdata) :
-  _cdata(cdata)
+  _stashed(cdata->get_stashed())
 {
 }
-#endif  // DO_PIPELINING
 
-#ifndef DO_PIPELINING
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::Stashed::Copy Constructor
 //       Access: Public
@@ -1065,12 +1113,10 @@ Stashed(const PandaNode::CDLinksReader &cdata) :
 ////////////////////////////////////////////////////////////////////
 INLINE PandaNode::Stashed::
 Stashed(const PandaNode::Stashed &copy) :
-  _cdata(copy._cdata)
+  _stashed(copy._stashed)
 {
 }
-#endif  // DO_PIPELINING
 
-#ifndef DO_PIPELINING
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::Stashed::Copy Assignment Operator
 //       Access: Public
@@ -1078,11 +1124,9 @@ Stashed(const PandaNode::Stashed &copy) :
 ////////////////////////////////////////////////////////////////////
 INLINE void PandaNode::Stashed::
 operator = (const PandaNode::Stashed &copy) {
-  _cdata = copy._cdata;
+  _stashed = copy._stashed;
 }
-#endif  // DO_PIPELINING
 
-#ifndef DO_PIPELINING
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::Stashed::get_num_stashed
 //       Access: Public
@@ -1090,11 +1134,9 @@ operator = (const PandaNode::Stashed &copy) {
 ////////////////////////////////////////////////////////////////////
 INLINE int PandaNode::Stashed::
 get_num_stashed() const {
-  return _cdata->_stashed.size();
+  return _stashed->size();
 }
-#endif  // DO_PIPELINING
 
-#ifndef DO_PIPELINING
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::Stashed::get_stashed
 //       Access: Public
@@ -1102,91 +1144,59 @@ get_num_stashed() const {
 ////////////////////////////////////////////////////////////////////
 INLINE PandaNode *PandaNode::Stashed::
 get_stashed(int n) const {
-  nassertr(n >= 0 && n < (int)_cdata->_stashed.size(), NULL);
-  return _cdata->_stashed[n].get_child();
+  nassertr(n >= 0 && n < (int)_stashed->size(), NULL);
+  return (*_stashed)[n].get_child();
 }
-#endif  // DO_PIPELINING
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::StashedCopy::Copy Constructor
+//     Function: PandaNode::Parents::Constructor
 //       Access: Public
 //  Description:
 ////////////////////////////////////////////////////////////////////
-INLINE PandaNode::StashedCopy::
-StashedCopy(const PandaNode::StashedCopy &copy) :
-  _list(copy._list)
+INLINE PandaNode::Parents::
+Parents(const PandaNode::CDLinksReader &cdata) :
+  _up(cdata->get_up())
 {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::StashedCopy::Copy Assignment Operator
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE void PandaNode::StashedCopy::
-operator = (const PandaNode::StashedCopy &copy) {
-  _list = copy._list;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::StashedCopy::get_num_stashed
-//       Access: Public
-//  Description: Returns the number of stashed children of the node.
-////////////////////////////////////////////////////////////////////
-INLINE int PandaNode::StashedCopy::
-get_num_stashed() const {
-  return _list.size();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::StashedCopy::get_stashed
-//       Access: Public
-//  Description: Returns the nth stashed child of the node.
-////////////////////////////////////////////////////////////////////
-INLINE PandaNode *PandaNode::StashedCopy::
-get_stashed(int n) const {
-  nassertr(n >= 0 && n < (int)_list.size(), NULL);
-  return _list[n];
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::ParentsCopy::Copy Constructor
+//     Function: PandaNode::Parents::Copy Constructor
 //       Access: Public
 //  Description:
 ////////////////////////////////////////////////////////////////////
-INLINE PandaNode::ParentsCopy::
-ParentsCopy(const PandaNode::ParentsCopy &copy) :
-  _list(copy._list)
+INLINE PandaNode::Parents::
+Parents(const PandaNode::Parents &copy) :
+  _up(copy._up)
 {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::ParentsCopy::Copy Assignment Operator
+//     Function: PandaNode::Parents::Copy Assignment Operator
 //       Access: Public
 //  Description:
 ////////////////////////////////////////////////////////////////////
-INLINE void PandaNode::ParentsCopy::
-operator = (const PandaNode::ParentsCopy &copy) {
-  _list = copy._list;
+INLINE void PandaNode::Parents::
+operator = (const PandaNode::Parents &copy) {
+  _up = copy._up;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::ParentsCopy::get_num_parents
+//     Function: PandaNode::Parents::get_num_parents
 //       Access: Public
 //  Description: Returns the number of parents of the node.
 ////////////////////////////////////////////////////////////////////
-INLINE int PandaNode::ParentsCopy::
+INLINE int PandaNode::Parents::
 get_num_parents() const {
-  return _list.size();
+  return _up->size();
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::ParentsCopy::get_parent
+//     Function: PandaNode::Parents::get_parent
 //       Access: Public
 //  Description: Returns the nth parent of the node.
 ////////////////////////////////////////////////////////////////////
-INLINE PandaNode *PandaNode::ParentsCopy::
+INLINE PandaNode *PandaNode::Parents::
 get_parent(int n) const {
-  nassertr(n >= 0 && n < (int)_list.size(), NULL);
-  return _list[n];
+  nassertr(n >= 0 && n < (int)_up->size(), NULL);
+  return (*_up)[n].get_parent();
 }

+ 211 - 169
panda/src/pgraph/pandaNode.cxx

@@ -27,12 +27,17 @@
 #include "accumulatedAttribs.h"
 #include "clipPlaneAttrib.h"
 #include "boundingSphere.h"
+#include "pStatTimer.h"
 
 // This category is just temporary for debugging convenience.
 NotifyCategoryDecl(drawmask, EXPCL_PANDA, EXPTP_PANDA);
 NotifyCategoryDef(drawmask, "");
 
+PandaNodeChain PandaNode::_dirty_prev_transforms;
 DrawMask PandaNode::_overall_bit = DrawMask::bit(31);
+
+PStatCollector PandaNode::_reset_prev_pcollector("App:Collisions:Reset");
+
 TypeHandle PandaNode::_type_handle;
 
 //
@@ -68,7 +73,8 @@ TypeHandle PandaNode::_type_handle;
 PandaNode::
 PandaNode(const string &name) :
   Namable(name),
-  _paths_lock("PandaNode::_paths_lock")
+  _paths_lock("PandaNode::_paths_lock"),
+  _dirty_prev_transform(false)
 {
   if (pgraph_cat.is_debug()) {
     pgraph_cat.debug()
@@ -90,13 +96,14 @@ PandaNode::
     pgraph_cat.debug()
       << "Destructing " << (void *)this << ", " << get_name() << "\n";
   }
+  clear_dirty_prev_transform();
 
   // We shouldn't have any parents left by the time we destruct, or
   // there's a refcount fault somewhere.
 #ifndef NDEBUG
   {
     CDLinksReader cdata(_cycler_links);
-    nassertv(cdata->_up.empty());
+    nassertv(cdata->get_up()->empty());
   }
 #endif  // NDEBUG
 
@@ -115,7 +122,8 @@ PandaNode(const PandaNode &copy) :
   ReferenceCount(copy),
   TypedWritable(copy),
   Namable(copy),
-  _paths_lock("PandaNode::_paths_lock")
+  _paths_lock("PandaNode::_paths_lock"),
+  _dirty_prev_transform(false)
 {
   if (pgraph_cat.is_debug()) {
     pgraph_cat.debug()
@@ -129,14 +137,17 @@ PandaNode(const PandaNode &copy) :
   // Copy the other node's state.
   {
     CDLightReader copy_cdata(copy._cycler_light);
-    CDLightWriter cdata(_cycler_light);
+    CDLightWriter cdata(_cycler_light, true);
     cdata->_state = copy_cdata->_state;
     cdata->_transform = copy_cdata->_transform;
     cdata->_prev_transform = copy_cdata->_prev_transform;
+    if (cdata->_transform != cdata->_prev_transform) {
+      set_dirty_prev_transform();
+    }
   }
   {
     CDHeavyReader copy_cdata(copy._cycler_heavy);
-    CDHeavyWriter cdata(_cycler_heavy);
+    CDHeavyWriter cdata(_cycler_heavy, true);
     cdata->_effects = copy_cdata->_effects;
     cdata->_tag_data = copy_cdata->_tag_data;
     cdata->_draw_control_mask = copy_cdata->_draw_control_mask;
@@ -565,16 +576,17 @@ add_child(PandaNode *child_node, int sort) {
 
   // Apply this operation to the current stage as well as to all
   // upstream stages.
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_links) {
-    CDLinksStageWriter cdata(_cycler_links, pipeline_stage);
-    CDLinksStageWriter cdata_child(child_node->_cycler_links, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_links, current_thread) {
+    CDLinksStageWriter cdata(_cycler_links, pipeline_stage, current_thread);
+    CDLinksStageWriter cdata_child(child_node->_cycler_links, pipeline_stage, current_thread);
     
-    cdata->_down.insert(DownConnection(child_node, sort));
-    cdata_child->_up.insert(UpConnection(this));
+    cdata->modify_down()->insert(DownConnection(child_node, sort));
+    cdata_child->modify_up()->insert(UpConnection(this));
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler_links);
 
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler_links) {
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler_links, current_thread) {
     new_connection(this, child_node, pipeline_stage);
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler_links);
@@ -595,13 +607,15 @@ remove_child(int child_index) {
   nassertv(pipeline_stage == 0);
 
   CDLinksStageWriter cdata(_cycler_links, pipeline_stage);
-  nassertv(child_index >= 0 && child_index < (int)cdata->_down.size());
+  Down &down = *cdata->modify_down();
+  nassertv(child_index >= 0 && child_index < (int)down.size());
   
-  PT(PandaNode) child_node = cdata->_down[child_index].get_child();
+  PT(PandaNode) child_node = down[child_index].get_child();
   CDLinksStageWriter cdata_child(child_node->_cycler_links, pipeline_stage);
+  Up &up = *cdata_child->modify_up();
 
-  cdata->_down.erase(cdata->_down.begin() + child_index);
-  int num_erased = cdata_child->_up.erase(UpConnection(this));
+  down.erase(down.begin() + child_index);
+  int num_erased = up.erase(UpConnection(this));
   nassertv(num_erased == 1);
 
   sever_connection(this, child_node, pipeline_stage);
@@ -630,7 +644,8 @@ remove_child(PandaNode *child_node) {
   // We have to do this for each upstream pipeline stage.
   bool any_removed = false;
 
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler_links) {
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler_links, current_thread) {
     if (stage_remove_child(child_node, pipeline_stage)) {
       any_removed = true;
 
@@ -674,7 +689,8 @@ replace_child(PandaNode *orig_child, PandaNode *new_child) {
   // We have to do this for each upstream pipeline stage.
   bool any_replaced = false;
 
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_links) {
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_links, current_thread) {
     if (stage_replace_child(orig_child, new_child, pipeline_stage)) {
       any_replaced = true;
     }
@@ -723,8 +739,8 @@ stash_child(int child_index) {
     CDLinksStageWriter cdata(_cycler_links, pipeline_stage);
     CDLinksStageWriter cdata_child(child_node->_cycler_links, pipeline_stage);
     
-    cdata->_stashed.insert(DownConnection(child_node, sort));
-    cdata_child->_up.insert(UpConnection(this));
+    cdata->modify_stashed()->insert(DownConnection(child_node, sort));
+    cdata_child->modify_up()->insert(UpConnection(this));
   }
 
   new_connection(this, child_node, pipeline_stage);
@@ -767,9 +783,9 @@ unstash_child(int stashed_index) {
   {
     CDLinksWriter cdata(_cycler_links);
     CDLinksWriter cdata_child(child_node->_cycler_links);
-    
-    cdata->_down.insert(DownConnection(child_node, sort));
-    cdata_child->_up.insert(UpConnection(this));
+
+    cdata->modify_down()->insert(DownConnection(child_node, sort));
+    cdata_child->modify_up()->insert(UpConnection(this));
   }
 
   int pipeline_stage = Thread::get_current_pipeline_stage();
@@ -806,8 +822,8 @@ add_stashed(PandaNode *child_node, int sort) {
     CDLinksWriter cdata(_cycler_links);
     CDLinksWriter cdata_child(child_node->_cycler_links);
     
-    cdata->_stashed.insert(DownConnection(child_node, sort));
-    cdata_child->_up.insert(UpConnection(this));
+    cdata->modify_stashed()->insert(DownConnection(child_node, sort));
+    cdata_child->modify_up()->insert(UpConnection(this));
   }
     
   int pipeline_stage = Thread::get_current_pipeline_stage();
@@ -829,13 +845,14 @@ remove_stashed(int child_index) {
   nassertv(pipeline_stage == 0);
 
   CDLinksStageWriter cdata(_cycler_links, pipeline_stage);
-  nassertv(child_index >= 0 && child_index < (int)cdata->_stashed.size());
+  Down &stashed = *cdata->modify_stashed();
+  nassertv(child_index >= 0 && child_index < (int)stashed.size());
   
-  PT(PandaNode) child_node = cdata->_stashed[child_index].get_child();
+  PT(PandaNode) child_node = stashed[child_index].get_child();
   CDLinksStageWriter cdata_child(child_node->_cycler_links, pipeline_stage);
 
-  cdata->_stashed.erase(cdata->_stashed.begin() + child_index);
-  int num_erased = cdata_child->_up.erase(UpConnection(this));
+  stashed.erase(stashed.begin() + child_index);
+  int num_erased = cdata_child->modify_up()->erase(UpConnection(this));
   nassertv(num_erased == 1);
 
   sever_connection(this, child_node, pipeline_stage);
@@ -857,29 +874,33 @@ remove_stashed(int child_index) {
 void PandaNode::
 remove_all_children() {
   // We have to do this for each upstream pipeline stage.
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_links) {
-    CDLinksStageWriter cdata(_cycler_links, pipeline_stage);
-    
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_links, current_thread) {
+    CDLinksStageWriter cdata(_cycler_links, pipeline_stage, current_thread);
+    Down &down = *cdata->modify_down();
     Down::iterator di;
-    for (di = cdata->_down.begin(); di != cdata->_down.end(); ++di) {
+    for (di = down.begin(); di != down.end(); ++di) {
       PT(PandaNode) child_node = (*di).get_child();
-      CDLinksStageWriter cdata_child(child_node->_cycler_links, pipeline_stage);
-      cdata_child->_up.erase(UpConnection(this));
+      CDLinksStageWriter cdata_child(child_node->_cycler_links, pipeline_stage,
+                                     current_thread);
+      cdata_child->modify_up()->erase(UpConnection(this));
       
       sever_connection(this, child_node, pipeline_stage);
       child_node->parents_changed();
     }
-    cdata->_down.clear();
+    down.clear();
     
-    for (di = cdata->_stashed.begin(); di != cdata->_stashed.end(); ++di) {
+    Down &stashed = *cdata->modify_stashed();
+    for (di = stashed.begin(); di != stashed.end(); ++di) {
       PT(PandaNode) child_node = (*di).get_child();
-      CDLinksStageWriter cdata_child(child_node->_cycler_links, pipeline_stage);
-      cdata_child->_up.erase(UpConnection(this));
+      CDLinksStageWriter cdata_child(child_node->_cycler_links, pipeline_stage,
+                                     current_thread);
+      cdata_child->modify_up()->erase(UpConnection(this));
       
       sever_connection(this, child_node, pipeline_stage);
       child_node->parents_changed();
     }
-    cdata->_stashed.clear();
+    stashed.clear();
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler_links);
 
@@ -965,8 +986,9 @@ set_attrib(const RenderAttrib *attrib, int override) {
   // Apply this operation to the current stage as well as to all
   // upstream stages.
   bool any_changed = false;
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_light) {
-    CDLightStageWriter cdata(_cycler_light, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_light, current_thread) {
+    CDLightStageWriter cdata(_cycler_light, pipeline_stage, current_thread);
     
     CPT(RenderState) new_state = cdata->_state->add_attrib(attrib, override);
     if (cdata->_state != new_state) {
@@ -995,8 +1017,9 @@ void PandaNode::
 clear_attrib(TypeHandle type) {
   bool any_changed = false;
 
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_light) {
-    CDLightStageWriter cdata(_cycler_light, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_light, current_thread) {
+    CDLightStageWriter cdata(_cycler_light, pipeline_stage, current_thread);
     
     CPT(RenderState) new_state = cdata->_state->remove_attrib(type);
     if (cdata->_state != new_state) {
@@ -1025,8 +1048,9 @@ void PandaNode::
 set_effect(const RenderEffect *effect) {
   // Apply this operation to the current stage as well as to all
   // upstream stages.
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy) {
-    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy, current_thread) {
+    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage, current_thread);
     cdata->_effects = cdata->_effects->add_effect(effect);
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy);
@@ -1040,8 +1064,9 @@ set_effect(const RenderEffect *effect) {
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
 clear_effect(TypeHandle type) {
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy) {
-    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy, current_thread) {
+    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage, current_thread);
     cdata->_effects = cdata->_effects->remove_effect(type);
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy);
@@ -1062,8 +1087,9 @@ set_state(const RenderState *state) {
   // Apply this operation to the current stage as well as to all
   // upstream stages.
   bool any_changed = false;
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_light) {
-    CDLightStageWriter cdata(_cycler_light, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_light, current_thread) {
+    CDLightStageWriter cdata(_cycler_light, pipeline_stage, current_thread);
     if (cdata->_state != state) {
       cdata->_state = state;
       any_changed = true;
@@ -1090,8 +1116,9 @@ void PandaNode::
 set_effects(const RenderEffects *effects) {
   // Apply this operation to the current stage as well as to all
   // upstream stages.
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy) {
-    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy, current_thread) {
+    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage, current_thread);
     cdata->_effects = effects;
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy);
@@ -1109,8 +1136,9 @@ set_transform(const TransformState *transform) {
   // Apply this operation to the current stage as well as to all
   // upstream stages.
   bool any_changed = false;
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_light) {
-    CDLightStageWriter cdata(_cycler_light, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_light, current_thread) {
+    CDLightStageWriter cdata(_cycler_light, pipeline_stage, current_thread);
     if (cdata->_transform != transform) {
       cdata->_transform = transform;
       any_changed = true;
@@ -1136,9 +1164,17 @@ void PandaNode::
 set_prev_transform(const TransformState *transform) {
   // Apply this operation to the current stage as well as to all
   // upstream stages.
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_light) {
-    CDLightStageWriter cdata(_cycler_light, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_light, current_thread) {
+    CDLightStageWriter cdata(_cycler_light, pipeline_stage, current_thread);
     cdata->_prev_transform = transform;
+    if (pipeline_stage == 0) {
+      if (cdata->_transform != cdata->_prev_transform) {
+        set_dirty_prev_transform();
+      } else {
+        clear_dirty_prev_transform();
+      }
+    }
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler_light);
 }
@@ -1146,24 +1182,63 @@ set_prev_transform(const TransformState *transform) {
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::reset_prev_transform
 //       Access: Published
-//  Description: Resets the "previous" transform on this node to be
-//               the same as the current transform.  This is not the
-//               same as clearing it to identity.
+//  Description: Resets the transform that represents this node's
+//               "previous" position to the same as the current
+//               transform.  This is not the same thing as clearing it
+//               to identity.
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
 reset_prev_transform() {
   // Apply this operation to the current stage as well as to all
   // upstream stages.
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_light) {
-    CDLightStageReader cdata(_cycler_light, pipeline_stage);
-    if (cdata->_prev_transform != cdata->_transform) {
-      CDLightStageWriter cdataw(_cycler_light, pipeline_stage, cdata);
-      cdataw->_prev_transform = cdataw->_transform;
-    }
+  Thread *current_thread = Thread::get_current_thread();
+
+  clear_dirty_prev_transform();
+
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_light, current_thread) {
+    CDLightStageWriter cdata(_cycler_light, pipeline_stage, current_thread);
+    cdata->_prev_transform = cdata->_transform;
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler_light);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::reset_all_prev_transform
+//       Access: Published, Static
+//  Description: Visits all nodes in the world with the
+//               _dirty_prev_transform flag--that is, all nodes whose
+//               _prev_transform is different from the _transform in
+//               pipeline stage 0--and resets the _prev_transform to
+//               be the same as _transform.
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+reset_all_prev_transform() {
+  Thread *current_thread = Thread::get_current_thread();
+  nassertv(current_thread->get_pipeline_stage() == 0);
+
+  PStatTimer timer(_reset_prev_pcollector);
+  MutexHolder holder(_dirty_prev_transforms._lock);
+
+  LinkedListNode *list_node = _dirty_prev_transforms._next;
+  while (list_node != &_dirty_prev_transforms) {
+    PandaNode *panda_node = (PandaNode *)list_node;
+    nassertv(panda_node->_dirty_prev_transform);
+    panda_node->_dirty_prev_transform = false;
+    
+    CDLightStageWriter cdata(panda_node->_cycler_light, 0, current_thread);
+    cdata->_prev_transform = cdata->_transform;
+
+    list_node = panda_node->_next;
+#ifndef NDEBUG
+    panda_node->_prev = NULL;
+    panda_node->_next = NULL;
+#endif  // NDEBUG
+  }
+
+  _dirty_prev_transforms._prev = &_dirty_prev_transforms;
+  _dirty_prev_transforms._next = &_dirty_prev_transforms;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::set_tag
 //       Access: Published
@@ -1181,8 +1256,9 @@ void PandaNode::
 set_tag(const string &key, const string &value) {
   // Apply this operation to the current stage as well as to all
   // upstream stages.
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy) {
-    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy, current_thread) {
+    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage, current_thread);
     cdata->_tag_data[key] = value;
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy);
@@ -1197,8 +1273,9 @@ set_tag(const string &key, const string &value) {
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
 clear_tag(const string &key) {
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy) {
-    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy, current_thread) {
+    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage, current_thread);
     cdata->_tag_data.erase(key);
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy);
@@ -1322,9 +1399,11 @@ copy_tags(PandaNode *other) {
 
   // Apply this operation to the current stage as well as to all
   // upstream stages.
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy) {
-    CDHeavyStageWriter cdataw(_cycler_heavy, pipeline_stage);
-    CDHeavyStageReader cdatar(other->_cycler_heavy, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy, current_thread) {
+    CDHeavyStageWriter cdataw(_cycler_heavy, pipeline_stage, current_thread);
+    CDHeavyStageReader cdatar(other->_cycler_heavy, pipeline_stage,
+                              current_thread);
       
     TagData::const_iterator ti;
     for (ti = cdatar->_tag_data.begin();
@@ -1444,8 +1523,9 @@ void PandaNode::
 adjust_draw_mask(DrawMask show_mask, DrawMask hide_mask, DrawMask clear_mask) {
   bool any_changed = false;
 
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy) {
-    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy, current_thread) {
+    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage, current_thread);
     
     DrawMask draw_control_mask = (cdata->_draw_control_mask | show_mask | hide_mask) & ~clear_mask;
     DrawMask draw_show_mask = (cdata->_draw_show_mask | show_mask) & ~hide_mask;
@@ -1543,8 +1623,9 @@ set_into_collide_mask(CollideMask mask) {
   mask &= get_legal_collide_mask();
 
   bool any_changed = false;
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy) {
-    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy, current_thread) {
+    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage, current_thread);
     if (cdata->_into_collide_mask != mask) {
       cdata->_into_collide_mask = mask;
       any_changed = true;
@@ -1686,8 +1767,9 @@ write(ostream &out, int indent_level) const {
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
 set_bounds(const BoundingVolume *volume) {
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy) {
-    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy, current_thread) {
+    CDHeavyStageWriter cdata(_cycler_heavy, pipeline_stage, current_thread);
     if (volume == NULL) {
       cdata->_user_bounds = NULL;
     } else {
@@ -1756,7 +1838,8 @@ get_bounds() const {
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
 mark_bounds_stale() const {
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler_links) {
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler_links, current_thread) {
     mark_bounds_stale(pipeline_stage);
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler_links);
@@ -1839,8 +1922,9 @@ get_internal_bounds(int pipeline_stage) const {
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
 set_internal_bounds(const BoundingVolume *volume) {
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy) {
-    CDHeavyStageWriter cdataw(_cycler_heavy, pipeline_stage);
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM(_cycler_heavy, current_thread) {
+    CDHeavyStageWriter cdataw(_cycler_heavy, pipeline_stage, current_thread);
     cdataw->_internal_bounds = volume;
     cdataw->_internal_bounds_stale = false;
   }
@@ -1861,7 +1945,8 @@ set_internal_bounds(const BoundingVolume *volume) {
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
 force_bounds_stale() {
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler_links) {
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler_links, current_thread) {
     force_bounds_stale(pipeline_stage);
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler_links);
@@ -1888,7 +1973,7 @@ force_bounds_stale(int pipeline_stage) {
   // It is similarly important that we use get_parents() here to copy
   // the parents list, instead of keeping the lock open while we walk
   // through the parents list directly on the node.
-  ParentsCopy parents = get_parents_copy();
+  Parents parents = get_parents();
   int num_parents = parents.get_num_parents();
   for (int i = 0; i < num_parents; ++i) {
     PandaNode *parent = parents.get_parent(i);
@@ -1910,7 +1995,8 @@ force_bounds_stale(int pipeline_stage) {
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
 mark_internal_bounds_stale() {
-  OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler_links) {
+  Thread *current_thread = Thread::get_current_thread();
+  OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler_links, current_thread) {
     mark_internal_bounds_stale(pipeline_stage);
   }
   CLOSE_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(_cycler_links);
@@ -2032,8 +2118,9 @@ r_copy_subgraph(PandaNode::InstanceMap &inst_map) const {
 void PandaNode::
 r_copy_children(const PandaNode *from, PandaNode::InstanceMap &inst_map) {
   CDLinksReader from_cdata(from->_cycler_links);
+  const Down &from_down = *from_cdata->get_down();
   Down::const_iterator di;
-  for (di = from_cdata->_down.begin(); di != from_cdata->_down.end(); ++di) {
+  for (di = from_down.begin(); di != from_down.end(); ++di) {
     int sort = (*di).get_sort();
     PandaNode *source_child = (*di).get_child();
     PT(PandaNode) dest_child;
@@ -2066,10 +2153,11 @@ do_find_child(PandaNode *node, const CDataLinks *cdata) const {
 
   // We have to search for the child by brute force, since we don't
   // know what sort index it was added as.
+  const Down &down = *cdata->get_down();
   Down::const_iterator di;
-  for (di = cdata->_down.begin(); di != cdata->_down.end(); ++di) {
+  for (di = down.begin(); di != down.end(); ++di) {
     if ((*di).get_child() == node) {
-      return di - cdata->_down.begin();
+      return di - down.begin();
     }
   }
 
@@ -2087,10 +2175,11 @@ do_find_stashed(PandaNode *node, const CDataLinks *cdata) const {
 
   // We have to search for the child by brute force, since we don't
   // know what sort index it was added as.
+  const Down &stashed = *cdata->get_stashed();
   Down::const_iterator di;
-  for (di = cdata->_stashed.begin(); di != cdata->_stashed.end(); ++di) {
+  for (di = stashed.begin(); di != stashed.end(); ++di) {
     if ((*di).get_child() == node) {
-      return di - cdata->_stashed.begin();
+      return di - stashed.begin();
     }
   }
 
@@ -2120,8 +2209,9 @@ stage_remove_child(PandaNode *child_node, int pipeline_stage) {
   int child_index = do_find_child(child_node, cdata);
   if (child_index >= 0) {
     // The child exists; remove it.
-    cdata->_down.erase(cdata->_down.begin() + child_index);
-    int num_erased = cdata_child->_up.erase(UpConnection(this));
+    Down &down = *cdata->modify_down();
+    down.erase(down.begin() + child_index);
+    int num_erased = cdata_child->modify_up()->erase(UpConnection(this));
     nassertr(num_erased == 1, false);
     return true;
   }
@@ -2129,8 +2219,9 @@ stage_remove_child(PandaNode *child_node, int pipeline_stage) {
   int stashed_index = do_find_stashed(child_node, cdata);
   if (stashed_index >= 0) {
     // The child has been stashed; remove it.
-    cdata->_stashed.erase(cdata->_stashed.begin() + stashed_index);
-    int num_erased = cdata_child->_up.erase(UpConnection(this));
+    Down &stashed = *cdata->modify_down();
+    stashed.erase(stashed.begin() + stashed_index);
+    int num_erased = cdata_child->modify_up()->erase(UpConnection(this));
     nassertr(num_erased == 1, false);
     return true;
   }
@@ -2175,7 +2266,7 @@ stage_replace_child(PandaNode *orig_child, PandaNode *new_child,
     int child_index = do_find_child(orig_child, cdata);
     if (child_index >= 0) {
       // The child exists; replace it.
-      DownConnection &down = cdata->_down[child_index];
+      DownConnection &down = (*cdata->modify_down())[child_index];
       nassertr(down.get_child() == orig_child, false);
       down.set_child(new_child);
       
@@ -2183,7 +2274,7 @@ stage_replace_child(PandaNode *orig_child, PandaNode *new_child,
       int stashed_index = do_find_stashed(orig_child, cdata);
       if (stashed_index >= 0) {
 	// The child has been stashed; remove it.
-	DownConnection &down = cdata->_stashed[stashed_index];
+	DownConnection &down = (*cdata->modify_stashed())[stashed_index];
 	nassertr(down.get_child() == orig_child, false);
 	down.set_child(new_child);
 	
@@ -2197,8 +2288,8 @@ stage_replace_child(PandaNode *orig_child, PandaNode *new_child,
     }
     
     // Now adjust the bookkeeping on both children.
-    cdata_new_child->_up.insert(UpConnection(this));
-    int num_erased = cdata_orig_child->_up.erase(UpConnection(this));
+    cdata_new_child->modify_up()->insert(UpConnection(this));
+    int num_erased = cdata_orig_child->modify_up()->erase(UpConnection(this));
     nassertr(num_erased == 1, false);
   }
 
@@ -2300,7 +2391,7 @@ detach_one_stage(NodePathComponent *child, int pipeline_stage) {
     
     // First, look for and remove the parent node from the child's up
     // list.
-    int num_erased = cdata_child->_up.erase(UpConnection(parent_node));
+    int num_erased = cdata_child->modify_up()->erase(UpConnection(parent_node));
     nassertv(num_erased == 1);
     
     // Now, look for and remove the child node from the parent's down
@@ -2308,19 +2399,17 @@ detach_one_stage(NodePathComponent *child, int pipeline_stage) {
     // has been stashed.
     Down::iterator di;
     bool found = false;
-    for (di = cdata_parent->_down.begin(); 
-	 di != cdata_parent->_down.end() && !found; 
-	 ++di) {
+    Down &down = *cdata_parent->modify_down();
+    for (di = down.begin(); di != down.end() && !found; ++di) {
       if ((*di).get_child() == child_node) {
-	cdata_parent->_down.erase(di);
+	down.erase(di);
 	found = true;
       }
     }
-    for (di = cdata_parent->_stashed.begin(); 
-	 di != cdata_parent->_stashed.end() && !found; 
-	 ++di) {
+    Down &stashed = *cdata_parent->modify_stashed();
+    for (di = stashed.begin(); di != stashed.end() && !found; ++di) {
       if ((*di).get_child() == child_node) {
-	cdata_parent->_stashed.erase(di);
+	stashed.erase(di);
 	found = true;
       }
     }
@@ -2417,11 +2506,11 @@ reparent_one_stage(NodePathComponent *new_parent, NodePathComponent *child,
       CDLinksStageWriter cdata_child(child_node->_cycler_links, pipeline_stage);
 
       if (as_stashed) {
-        cdata_parent->_stashed.insert(DownConnection(child_node, sort));
+        cdata_parent->modify_stashed()->insert(DownConnection(child_node, sort));
       } else {
-        cdata_parent->_down.insert(DownConnection(child_node, sort));
+        cdata_parent->modify_down()->insert(DownConnection(child_node, sort));
       }
-      cdata_child->_up.insert(UpConnection(parent_node));
+      cdata_child->modify_up()->insert(UpConnection(parent_node));
 
 #ifndef NDEBUG
       // The NodePathComponent should already be in the set.
@@ -2569,7 +2658,7 @@ r_get_generic_component(bool accept_ambiguity, bool &ambiguity_detected,
   {
     CDLinksStageReader cdata(_cycler_links, pipeline_stage);
     
-    int num_parents = cdata->_up.size();
+    int num_parents = cdata->get_up()->size();
     if (num_parents == 0) {
       // No parents; no ambiguity.  This is the root.
       return get_top_component(this, true, pipeline_stage);
@@ -2590,7 +2679,8 @@ r_get_generic_component(bool accept_ambiguity, bool &ambiguity_detected,
         << " parents; choosing arbitrary path to root.\n";
     }
     ambiguity_detected = true;
-    parent_node = cdata->_up[0].get_parent();
+    const Up &up = *cdata->get_up();
+    parent_node = up[0].get_parent();
   }
 
   // Now that the lock is released, it's safe to recurse.
@@ -3453,9 +3543,9 @@ make_copy() const {
 ////////////////////////////////////////////////////////////////////
 void PandaNode::CDataLinks::
 write_datagram(BamWriter *manager, Datagram &dg) const {
-  write_up_list(_up, manager, dg);
-  write_down_list(_down, manager, dg);
-  write_down_list(_stashed, manager, dg);
+  write_up_list(*get_up(), manager, dg);
+  write_down_list(*get_down(), manager, dg);
+  write_down_list(*get_stashed(), manager, dg);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -3470,9 +3560,9 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) {
   int pi = CycleData::complete_pointers(p_list, manager);
 
   // Get the parent and child pointers.
-  pi += complete_up_list(_up, p_list + pi, manager);
-  pi += complete_down_list(_down, p_list + pi, manager);
-  pi += complete_down_list(_stashed, p_list + pi, manager);
+  pi += complete_up_list(*modify_up(), p_list + pi, manager);
+  pi += complete_down_list(*modify_down(), p_list + pi, manager);
+  pi += complete_down_list(*modify_stashed(), p_list + pi, manager);
 
   return pi;
 }
@@ -3486,9 +3576,9 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) {
 ////////////////////////////////////////////////////////////////////
 void PandaNode::CDataLinks::
 fillin(DatagramIterator &scan, BamReader *manager) {
-  fillin_up_list(_up, scan, manager);
-  fillin_down_list(_down, scan, manager);
-  fillin_down_list(_stashed, scan, manager);
+  fillin_up_list(*modify_up(), scan, manager);
+  fillin_down_list(*modify_down(), scan, manager);
+  fillin_down_list(*modify_stashed(), scan, manager);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -3620,10 +3710,10 @@ fillin_up_list(PandaNode::Up &up_list,
                DatagramIterator &scan, BamReader *manager) {
   int num_parents = scan.get_uint16();
   // Read the list of parent nodes.  Push back a NULL for each one.
-  _up.reserve(num_parents);
+  up_list.reserve(num_parents);
   for (int i = 0; i < num_parents; i++) {
     manager->read_pointer(scan);
-    _up.push_back(UpConnection(NULL));
+    up_list.push_back(UpConnection(NULL));
   }
 }
 
@@ -3646,51 +3736,3 @@ fillin_down_list(PandaNode::Down &down_list,
     down_list.push_back(DownConnection(NULL, sort));
   }
 }
-
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::ChildrenCopy::Constructor
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-PandaNode::ChildrenCopy::
-ChildrenCopy(const PandaNode::CDLinksReader &cdata) {
-  _list.reserve(cdata->_down.size());
-
-  Down::const_iterator di;
-  for (di = cdata->_down.begin(); di != cdata->_down.end(); ++di) {
-    PandaNode *child = (*di).get_child();
-    _list.push_back(child);
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::StashedCopy::Constructor
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-PandaNode::StashedCopy::
-StashedCopy(const PandaNode::CDLinksReader &cdata) {
-  _list.reserve(cdata->_down.size());
-
-  Down::const_iterator di;
-  for (di = cdata->_stashed.begin(); di != cdata->_stashed.end(); ++di) {
-    PandaNode *child = (*di).get_child();
-    _list.push_back(child);
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::ParentsCopy::Constructor
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-PandaNode::ParentsCopy::
-ParentsCopy(const PandaNode::CDLinksReader &cdata) {
-  _list.reserve(cdata->_up.size());
-
-  Up::const_iterator ui;
-  for (ui = cdata->_up.begin(); ui != cdata->_up.end(); ++ui) {
-    PandaNode *parent = (*ui).get_parent();
-    _list.push_back(parent);
-  }
-}

+ 48 - 70
panda/src/pgraph/pandaNode.h

@@ -43,6 +43,8 @@
 #include "pnotify.h"
 #include "updateSeq.h"
 #include "deletedChain.h"
+#include "pandaNodeChain.h"
+#include "pStatCollector.h"
 
 #ifdef HAVE_PYTHON
 
@@ -66,7 +68,8 @@ class GeomTransformer;
 //               is the base class of all specialized nodes, and also
 //               serves as a generic node with no special properties.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA PandaNode : public TypedWritable, public Namable,
+class EXPCL_PANDA PandaNode : public TypedWritable, public Namable, 
+                              public LinkedListNode,
                               virtual public ReferenceCount {
 PUBLISHED:
   PandaNode(const string &name);
@@ -169,6 +172,8 @@ PUBLISHED:
   void set_prev_transform(const TransformState *transform);
   INLINE const TransformState *get_prev_transform() const;
   void reset_prev_transform();
+  INLINE bool has_dirty_prev_transform() const;
+  static void reset_all_prev_transform();
 
   void set_tag(const string &key, const string &value);
   INLINE string get_tag(const string &key) const;
@@ -303,6 +308,9 @@ private:
   void fix_path_lengths(int pipeline_stage);
   void r_list_descendants(ostream &out, int indent_level) const;
 
+  INLINE void set_dirty_prev_transform();
+  INLINE void clear_dirty_prev_transform();
+
 public:
   // This must be declared public so that VC6 will allow the nested
   // CData class to access it.
@@ -323,7 +331,7 @@ public:
   };
 
 private:
-  typedef ov_multiset<DownConnection> Down;
+  typedef RefCountObj< ov_multiset<DownConnection> > Down;
 
   class EXPCL_PANDA UpConnection {
   public:
@@ -336,7 +344,7 @@ private:
     // children do not circularly reference each other.
     PandaNode *_parent;
   };
-  typedef ov_set<UpConnection> Up;
+  typedef RefCountObj< ov_set<UpConnection> > Up;
 
   // We also maintain a set of NodePathComponents in the node.  This
   // represents the set of instances of this node that we have
@@ -352,6 +360,9 @@ private:
   Paths _paths;
   Mutex _paths_lock;
 
+  bool _dirty_prev_transform;
+  static PandaNodeChain _dirty_prev_transforms;
+
   // This is used to maintain a table of keyed data on each node, for
   // the user's purposes.
   typedef phash_map<string, string, string_hash> TagData;
@@ -368,7 +379,7 @@ private:
   // are grouped into a different one.
 
   // The CDataLight object stores the lightweight parts of the node
-  // that are likely to change fairly often: transform and stage.
+  // that are likely to change fairly often: transform and state.
   class EXPCL_PANDA CDataLight : public CycleData {
   public:
     INLINE CDataLight();
@@ -529,9 +540,19 @@ private:
     void fillin_down_list(Down &down_list,
                           DatagramIterator &scan, BamReader *manager);
 
-    Down _down;
-    Down _stashed;
-    Up _up;
+    INLINE const Down *get_down() const;
+    INLINE Down *modify_down();
+    INLINE const Down *get_stashed() const;
+    INLINE Down *modify_stashed();
+    INLINE const Up *get_up() const;
+    INLINE Up *modify_up();
+
+  private:
+    // We store the child lists by reference, so we can copy them
+    // quickly.  We perform copy-on-write when necessary.
+    PT(Down) _down;
+    PT(Down) _stashed;
+    PT(Up) _up;
   };
 
   PipelineCycler<CDataLinks> _cycler_links;
@@ -545,37 +566,16 @@ private:
 
   static DrawMask _overall_bit;
 
-public:
-  // This class is returned from get_children_copy().  Use it to walk
-  // through the list of children, particularly with a self-modifying
-  // loop, since the list of children is copied first and is thus safe
-  // from self modification.
-  class EXPCL_PANDA ChildrenCopy {
-  public:
-    ChildrenCopy(const CDLinksReader &cdata);
-    INLINE ChildrenCopy(const ChildrenCopy &copy);
-    INLINE void operator = (const ChildrenCopy &copy);
+  static PStatCollector _reset_prev_pcollector;
 
-    INLINE int get_num_children() const;
-    INLINE PandaNode *get_child(int n) const;
-
-  private:
-    typedef PTA(PT(PandaNode)) List;
-    List _list;
-  };
-
-  // This class is returned from get_children().  Use this interface
-  // when you want to walk through the list of children with less
-  // overhead than get_children_copy(); in single-threaded Panda, a
-  // copy is not made first, so this interface is fast.  However, this
-  // does not protect you from self-modifying loops.  Also, since the
-  // lock is kept open the whole time, this is generally unsafe to use
-  // in threaded code (which would then hold the lock on the root node
-  // during an entire traversal), so when pipelining is enabled,
-  // get_children() returns the same thing as get_children_copy().
-#ifdef DO_PIPELINING
-  typedef ChildrenCopy Children;
-#else
+public:
+  // This class is returned from get_children().  Use it to walk
+  // through the list of children.  This is faster, and safer, than
+  // walking through the children one at a time via
+  // get_num_children()/get_child(), since the list of children is
+  // saved out ahead of time, rather than having to reacquire the lock
+  // with each iteration, or to keep the lock held for the entire
+  // pass.
   class EXPCL_PANDA Children {
   public:
     INLINE Children(const CDLinksReader &cdata);
@@ -586,28 +586,10 @@ public:
     INLINE PandaNode *get_child(int n) const;
 
   private:
-    CDLinksReader _cdata;
+    CPT(Down) _down;
   };
-#endif  // DO_PIPELINING
 
   // Similarly for stashed children.
-  class EXPCL_PANDA StashedCopy {
-  public:
-    StashedCopy(const CDLinksReader &cdata);
-    INLINE StashedCopy(const StashedCopy &copy);
-    INLINE void operator = (const StashedCopy &copy);
-
-    INLINE int get_num_stashed() const;
-    INLINE PandaNode *get_stashed(int n) const;
-
-  private:
-    typedef PTA(PT(PandaNode)) List;
-    List _list;
-  };
-
-#ifdef DO_PIPELINING
-  typedef StashedCopy Stashed;
-#else
   class EXPCL_PANDA Stashed {
   public:
     INLINE Stashed(const CDLinksReader &cdata);
@@ -618,30 +600,26 @@ public:
     INLINE PandaNode *get_stashed(int n) const;
 
   private:
-    CDLinksReader _cdata;
+    CPT(Down) _stashed;
   };
-#endif  // DO_PIPELINING
 
-  // This class is returned from get_parents_copy().
-  class EXPCL_PANDA ParentsCopy {
+  // This class is returned from get_parents().
+  class EXPCL_PANDA Parents {
   public:
-    ParentsCopy(const CDLinksReader &cdata);
-    INLINE ParentsCopy(const ParentsCopy &copy);
-    INLINE void operator = (const ParentsCopy &copy);
+    INLINE Parents(const CDLinksReader &cdata);
+    INLINE Parents(const Parents &copy);
+    INLINE void operator = (const Parents &copy);
 
     INLINE int get_num_parents() const;
     INLINE PandaNode *get_parent(int n) const;
 
   private:
-    typedef PTA(PT(PandaNode)) List;
-    List _list;
+    CPT(Up) _up;
   };
 
-  INLINE ChildrenCopy get_children_copy() const;
-  INLINE Children get_children() const;
-  INLINE StashedCopy get_stashed_copy() const;
-  INLINE Stashed get_stashed() const;
-  INLINE ParentsCopy get_parents_copy() const;
+  INLINE Children get_children(Thread *current_thread = Thread::get_current_thread()) const;
+  INLINE Stashed get_stashed(Thread *current_thread = Thread::get_current_thread()) const;
+  INLINE Parents get_parents(Thread *current_thread = Thread::get_current_thread()) const;
 
 public:
   static void register_with_read_factory();

+ 1 - 0
panda/src/pgraph/pgraph_composite3.cxx

@@ -19,6 +19,7 @@
 #include "nodePathComponent.cxx"
 #include "nodePathLerps.cxx"
 #include "pandaNode.cxx"
+#include "pandaNodeChain.cxx"
 #include "planeNode.cxx"
 #include "pointLight.cxx"
 #include "polylightNode.cxx"

+ 2 - 2
panda/src/pgraph/sceneGraphReducer.cxx

@@ -62,7 +62,7 @@ flatten(PandaNode *root, int combine_siblings_bits) {
 
     // Get a copy of the children list, so we don't have to worry
     // about self-modifications.
-    PandaNode::ChildrenCopy cr = root->get_children_copy();
+    PandaNode::Children cr = root->get_children();
 
     // Now visit each of the children in turn.
     int num_children = cr.get_num_children();
@@ -257,7 +257,7 @@ r_flatten(PandaNode *grandparent_node, PandaNode *parent_node,
 
     // First, recurse on each of the children.
     {
-      PandaNode::ChildrenCopy cr = parent_node->get_children_copy();
+      PandaNode::Children cr = parent_node->get_children();
       int num_children = cr.get_num_children();
       for (int i = 0; i < num_children; i++) {
         PandaNode *child_node = cr.get_child(i);

+ 9 - 9
panda/src/pipeline/pipelineCycler.h

@@ -95,10 +95,10 @@ private:
 
 // Iterates through all of the pipeline stages upstream of the current
 // stage, but not including the current stage.
-#define OPEN_ITERATE_UPSTREAM_ONLY(cycler) {                        \
+#define OPEN_ITERATE_UPSTREAM_ONLY(cycler, current_thread) {        \
     CyclerHolder cholder(cycler);                                   \
     int pipeline_stage;                                             \
-    for (pipeline_stage = Thread::get_current_pipeline_stage() - 1; \
+    for (pipeline_stage = current_thread->get_pipeline_stage() - 1; \
          pipeline_stage >= 0;                                       \
          --pipeline_stage)
 
@@ -107,10 +107,10 @@ private:
 
 // Iterates through all of the pipeline stages upstream of the current
 // stage, and including the current stage.
-#define OPEN_ITERATE_CURRENT_AND_UPSTREAM(cycler) {                 \
+#define OPEN_ITERATE_CURRENT_AND_UPSTREAM(cycler, current_thread) { \
     CyclerHolder cholder(cycler);                                   \
     int pipeline_stage;                                             \
-    for (pipeline_stage = Thread::get_current_pipeline_stage();	    \
+    for (pipeline_stage = current_thread->get_pipeline_stage();	    \
          pipeline_stage >= 0;                                       \
          --pipeline_stage)
 
@@ -118,9 +118,9 @@ private:
   }
 
 // As above, but without holding the cycler lock during the loop.
-#define OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(cycler) {	    \
+#define OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(cycler, current_thread) {  \
     int pipeline_stage;                                             \
-    for (pipeline_stage = Thread::get_current_pipeline_stage();	    \
+    for (pipeline_stage = current_thread->get_pipeline_stage();    \
          pipeline_stage >= 0;                                       \
          --pipeline_stage)
 
@@ -142,20 +142,20 @@ private:
 // These are trivial implementations of the above macros, defined when
 // pipelining is not enabled, that simply operate on stage 0 without
 // bothering to create a for loop.
-#define OPEN_ITERATE_UPSTREAM_ONLY(cycler)      \
+#define OPEN_ITERATE_UPSTREAM_ONLY(cycler, current_thread)      \
   if (false) {                                  \
     const int pipeline_stage = -1;                   
 
 #define CLOSE_ITERATE_UPSTREAM_ONLY(cycler)     \
   }
 
-#define OPEN_ITERATE_CURRENT_AND_UPSTREAM(cycler) {                     \
+#define OPEN_ITERATE_CURRENT_AND_UPSTREAM(cycler, current_thread) {                     \
     const int pipeline_stage = 0;                                       \
     
 #define CLOSE_ITERATE_CURRENT_AND_UPSTREAM(cycler)      \
   }
 
-#define OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(cycler) {		\
+#define OPEN_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(cycler, current_thread) {  \
     const int pipeline_stage = 0;                                       \
     
 #define CLOSE_ITERATE_CURRENT_AND_UPSTREAM_NOLOCK(cycler)	\