Browse Source

add ObjectDeletor; avoid unnecessary locks on RenderState

David Rose 19 years ago
parent
commit
1fabf70443

+ 10 - 0
panda/src/display/graphicsEngine.cxx

@@ -39,6 +39,7 @@
 #include "thread.h"
 #include "thread.h"
 #include "pipeline.h"
 #include "pipeline.h"
 #include "throw_event.h"
 #include "throw_event.h"
+#include "objectDeletor.h"
 
 
 #if defined(WIN32)
 #if defined(WIN32)
   #define WINDOWS_LEAN_AND_MEAN
   #define WINDOWS_LEAN_AND_MEAN
@@ -68,6 +69,7 @@ PStatCollector GraphicsEngine::_render_states_pcollector("RenderStates");
 PStatCollector GraphicsEngine::_render_states_unused_pcollector("RenderStates:Unused");
 PStatCollector GraphicsEngine::_render_states_unused_pcollector("RenderStates:Unused");
 PStatCollector GraphicsEngine::_cyclers_pcollector("PipelineCyclers");
 PStatCollector GraphicsEngine::_cyclers_pcollector("PipelineCyclers");
 PStatCollector GraphicsEngine::_dirty_cyclers_pcollector("Dirty PipelineCyclers");
 PStatCollector GraphicsEngine::_dirty_cyclers_pcollector("Dirty PipelineCyclers");
+PStatCollector GraphicsEngine::_delete_pcollector("App:Delete");
 
 
 // These are counted independently by the collision system; we
 // These are counted independently by the collision system; we
 // redefine them here so we can reset them at each frame.
 // redefine them here so we can reset them at each frame.
@@ -631,6 +633,14 @@ render_frame() {
 
 
 #endif  // THREADED_PIPELINE && DO_PSTATS
 #endif  // THREADED_PIPELINE && DO_PSTATS
 
 
+  // If there is an object deletor, tell it to flush now, while we're
+  // between frames.
+  ObjectDeletor *deletor = ObjectDeletor::get_global_ptr();
+  if (deletor != (ObjectDeletor *)NULL) {
+    PStatTimer timer(_delete_pcollector);
+    deletor->flush();
+  }
+  
   GeomCacheManager::flush_level();
   GeomCacheManager::flush_level();
   CullTraverser::flush_level();
   CullTraverser::flush_level();
   RenderState::flush_level();
   RenderState::flush_level();

+ 1 - 0
panda/src/display/graphicsEngine.h

@@ -359,6 +359,7 @@ private:
   static PStatCollector _render_states_unused_pcollector;
   static PStatCollector _render_states_unused_pcollector;
   static PStatCollector _cyclers_pcollector;
   static PStatCollector _cyclers_pcollector;
   static PStatCollector _dirty_cyclers_pcollector;
   static PStatCollector _dirty_cyclers_pcollector;
+  static PStatCollector _delete_pcollector;
 
 
   static PStatCollector _cnode_volume_pcollector;
   static PStatCollector _cnode_volume_pcollector;
   static PStatCollector _gnode_volume_pcollector;
   static PStatCollector _gnode_volume_pcollector;

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

@@ -30,6 +30,7 @@
     multifile.I multifile.h \
     multifile.I multifile.h \
     namable.I \
     namable.I \
     namable.h nativeNumericData.I nativeNumericData.h \
     namable.h nativeNumericData.I nativeNumericData.h \
+    objectDeletor.h objectDeletor.I \
     ordered_vector.h ordered_vector.I ordered_vector.T \
     ordered_vector.h ordered_vector.I ordered_vector.T \
     password_hash.h \
     password_hash.h \
     patchfile.I patchfile.h \
     patchfile.I patchfile.h \
@@ -77,6 +78,7 @@
     memoryUsagePointers.cxx multifile.cxx \
     memoryUsagePointers.cxx multifile.cxx \
     namable.cxx \
     namable.cxx \
     nativeNumericData.cxx \
     nativeNumericData.cxx \
+    objectDeletor.cxx \
     ordered_vector.cxx \
     ordered_vector.cxx \
     password_hash.cxx \
     password_hash.cxx \
     patchfile.cxx \
     patchfile.cxx \
@@ -130,6 +132,7 @@
     multifile.I multifile.h \
     multifile.I multifile.h \
     namable.I \
     namable.I \
     namable.h nativeNumericData.I nativeNumericData.h \
     namable.h nativeNumericData.I nativeNumericData.h \
+    objectDeletor.h objectDeletor.I \
     ordered_vector.h ordered_vector.I ordered_vector.T \
     ordered_vector.h ordered_vector.I ordered_vector.T \
     password_hash.h \
     password_hash.h \
     patchfile.I patchfile.h \
     patchfile.I patchfile.h \

+ 0 - 39
panda/src/express/config_express.cxx

@@ -168,45 +168,6 @@ init_libexpress() {
 #endif
 #endif
 }
 }
 
 
-
-bool
-get_leak_memory() {
-  static ConfigVariableBool *leak_memory = NULL;
-
-  if (leak_memory == (ConfigVariableBool *)NULL) {
-    leak_memory = new ConfigVariableBool
-      ("leak-memory", false,
-       PRC_DESC("Set leak-memory true to disable the actual deletion of "
-                "ReferenceCount-derived objects.  This is sometimes useful to track "
-                "a reference counting bug, since the formerly deleted objects will "
-                "still remain (with a reference count of -100) without being "
-                "overwritten with a newly-allocated object, and the assertion tests "
-                "in ReferenceCount may more accurately detect the first instance of "
-                "an error."));
-  }
-
-  return *leak_memory;
-}
-
-bool
-get_never_destruct() {
-  static ConfigVariableBool *never_destruct = NULL;
-
-  if (never_destruct == (ConfigVariableBool *)NULL) {
-    never_destruct = new ConfigVariableBool
-      ("never-destruct", false,
-       PRC_DESC("never-destruct is similar to leak-memory, except that not "
-                "only will memory not be freed, but the destructor will not even be "
-                "called (on ReferenceCount objects, at least).  This will leak gobs "
-                "of memory, but ensures that every pointer to a ReferenceCount "
-                "object will always be valid, and may be useful for tracking down "
-                "certain kinds of errors.  "
-                "never-destruct is only respected if leak-memory is true."));
-  }
-
-  return *never_destruct;
-}
-
 bool
 bool
 get_use_high_res_clock() {
 get_use_high_res_clock() {
   static ConfigVariableBool *use_high_res_clock = NULL;
   static ConfigVariableBool *use_high_res_clock = NULL;

+ 0 - 2
panda/src/express/config_express.h

@@ -51,8 +51,6 @@ NotifyCategoryDecl(express, EXPCL_PANDAEXPRESS, EXPTP_PANDAEXPRESS);
 
 
 //extern EXPCL_PANDAEXPRESS const bool track_memory_usage;
 //extern EXPCL_PANDAEXPRESS const bool track_memory_usage;
 
 
-EXPCL_PANDAEXPRESS bool get_leak_memory();
-EXPCL_PANDAEXPRESS bool get_never_destruct();
 EXPCL_PANDAEXPRESS bool get_use_high_res_clock();
 EXPCL_PANDAEXPRESS bool get_use_high_res_clock();
 EXPCL_PANDAEXPRESS bool get_paranoid_clock();
 EXPCL_PANDAEXPRESS bool get_paranoid_clock();
 EXPCL_PANDAEXPRESS bool get_paranoid_inheritance();
 EXPCL_PANDAEXPRESS bool get_paranoid_inheritance();

+ 1 - 0
panda/src/express/express_composite1.cxx

@@ -18,6 +18,7 @@
 #include "multifile.cxx"
 #include "multifile.cxx"
 #include "namable.cxx"
 #include "namable.cxx"
 #include "nativeNumericData.cxx"
 #include "nativeNumericData.cxx"
+#include "objectDeletor.cxx"
 #include "ordered_vector.cxx"
 #include "ordered_vector.cxx"
 #include "patchfile.cxx"
 #include "patchfile.cxx"
 #include "password_hash.cxx"
 #include "password_hash.cxx"

+ 62 - 0
panda/src/express/objectDeletor.I

@@ -0,0 +1,62 @@
+// Filename: objectDeletor.I
+// Created by:  drose (10Apr06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjectDeletor::get_global_ptr
+//       Access: Public, Static
+//  Description: Returns the global ObjectDeletor object.  This may
+//               return NULL if there is no such object.
+////////////////////////////////////////////////////////////////////
+INLINE ObjectDeletor *ObjectDeletor::
+get_global_ptr() {
+  return (ObjectDeletor *)AtomicAdjust::get_ptr(_global_ptr);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjectDeletor::set_global_ptr
+//       Access: Public, Static
+//  Description: Assigns the global ObjectDeletor object.  Returns
+//               the pointer to the previous object, if any.
+////////////////////////////////////////////////////////////////////
+INLINE ObjectDeletor *ObjectDeletor::
+set_global_ptr(ObjectDeletor *ptr) {
+  return (ObjectDeletor *)AtomicAdjust::set_ptr(_global_ptr, ptr);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjectDeletor::DeleteToken::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE ObjectDeletor::DeleteToken::
+DeleteToken(DeleteFunc *func, void *ptr) :
+  _func(func),
+  _ptr(ptr)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjectDeletor::DeleteToken::do_delete
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void ObjectDeletor::DeleteToken::
+do_delete() {
+  (*_func)(_ptr);
+}

+ 84 - 0
panda/src/express/objectDeletor.cxx

@@ -0,0 +1,84 @@
+// Filename: objectDeletor.h
+// Created by:  drose (10Apr06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "objectDeletor.h"
+#include "configVariableString.h"
+
+void *ObjectDeletor::_global_ptr = NULL;
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjectDeletor::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+ObjectDeletor::
+~ObjectDeletor() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjectDeletor::delete_object
+//       Access: Public, Virtual
+//  Description: Adds the pointer to the object to be deleted, along
+//               with a pointer to a function that can delete it.
+////////////////////////////////////////////////////////////////////
+void ObjectDeletor::
+delete_object(DeleteFunc *func, void *ptr) {
+  // The base class functionality simply deletes the pointer
+  // immediately.
+  (*func)(ptr);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjectDeletor::flush
+//       Access: Public, Virtual
+//  Description: Ensures that any objects queued up for deletion have
+//               been fully deleted by the time flush() returns.
+////////////////////////////////////////////////////////////////////
+void ObjectDeletor::
+flush() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ObjectDeletor::register_subclass
+//       Access: Public, Static
+//  Description: Called at static init time as each subclass is
+//               defined.  The purpose here is to consult the config
+//               variable object-deletor and install the requested
+//               deletor.  If the indicated deletor is the one that is
+//               named by the config variable, it is installed.
+////////////////////////////////////////////////////////////////////
+void ObjectDeletor::
+register_subclass(ObjectDeletor *deletor, const string &name) {
+  ConfigVariableString object_deletor
+    ("object-deletor", "",
+     PRC_DESC("Specify the type of ObjectDeletor to install.  This string "
+              "must match one of the existing ObjectDeletor types, and if "
+              "it does not match, no error message is generated.  To "
+              "determine if the ObjectDeletor was properly installed, "
+              "set notify-level-express debug."));
+
+  if (object_deletor == name) {
+    if (express_cat.is_debug()) {
+      express_cat.debug()
+        << "Installing ObjectDeletor type " << name << "\n";
+    }
+    set_global_ptr(deletor);
+  } else {
+    delete deletor;
+  }
+}

+ 72 - 0
panda/src/express/objectDeletor.h

@@ -0,0 +1,72 @@
+// Filename: objectDeletor.h
+// Created by:  drose (10Apr06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef OBJECTDELETOR_H
+#define OBJECTDELETOR_H
+
+#include "pandabase.h"
+#include "atomicAdjust.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : ObjectDeletor
+// Description : This class is used to collect together pointers to
+//               objects that are ready to be destructed and freed.
+//               The actual destruction may be performed immediately,
+//               or it can be performed at some later time, when it is
+//               convenient for the application.
+//
+//               This is particularly useful for a multithreaded
+//               application; the destruction may be performed in a
+//               sub-thread with a lower priority.
+//
+//               This class body is just an interface; the
+//               ObjectDeletor class simply deletes its pointers
+//               immediately.  More sophisticated deletors are
+//               implemented elsewhere.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDAEXPRESS ObjectDeletor {
+public:
+  virtual ~ObjectDeletor();
+
+  typedef void DeleteFunc(void *ptr);
+
+  virtual void delete_object(DeleteFunc *func, void *ptr);
+  virtual void flush();
+
+  INLINE static ObjectDeletor *get_global_ptr();
+  INLINE static ObjectDeletor *set_global_ptr(ObjectDeletor *ptr);
+
+  static void register_subclass(ObjectDeletor *deletor, const string &name);
+
+protected:
+  class DeleteToken {
+  public:
+    INLINE DeleteToken(DeleteFunc *func, void *ptr);
+    INLINE void do_delete();
+
+    DeleteFunc *_func;
+    void *_ptr;
+  };
+
+private:
+  static void *_global_ptr;
+};
+
+#include "objectDeletor.I"
+
+#endif

+ 83 - 14
panda/src/express/referenceCount.I

@@ -332,6 +332,41 @@ weak_unref(WeakPointerToVoid *ptv) {
   _weak_list->clear_reference(ptv);
   _weak_list->clear_reference(ptv);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: DeleteWrapper::do_delete
+//       Access: Public, Static
+//  Description: This helper function encapsulates calling the
+//               destructor and delete operator for the given pointer.
+//               A pointer to this function is passed to the global
+//               ObjectDeletor, when there is one.
+////////////////////////////////////////////////////////////////////
+template<class ObjectType>
+void DeleteWrapper<ObjectType>::
+do_delete(void *ptr) {
+  TAU_PROFILE("void DeleteWrapper::do_delete(void *)", " ", TAU_USER);
+  delete ((ObjectType *)ptr);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: RefCountDeleteWrapper::do_delete
+//       Access: Public, Static
+//  Description: This is similar to DeleteWrapper::do_delete(), above,
+//               except it is specialized for an object that inherits
+//               from ReferenceCount, and it will check that some
+//               other pointer hasn't incremented the reference count
+//               in the interim before the pointer is actually
+//               deleted.
+////////////////////////////////////////////////////////////////////
+template<class RefCountType>
+void RefCountDeleteWrapper<RefCountType>::
+do_delete(void *ptr) {
+  TAU_PROFILE("void RefCountDeleteWrapper::do_delete(void *)", " ", TAU_USER);
+  if (!((RefCountType *)ptr)->unref()) {
+    // The reference count has reached 0; time to delete the pointer.
+    delete ((RefCountType *)ptr);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: unref_delete
 //     Function: unref_delete
 //  Description: This global helper function will unref the given
 //  Description: This global helper function will unref the given
@@ -345,32 +380,66 @@ weak_unref(WeakPointerToVoid *ptv) {
 template<class RefCountType>
 template<class RefCountType>
 INLINE void
 INLINE void
 unref_delete(RefCountType *ptr) {
 unref_delete(RefCountType *ptr) {
-  TAU_PROFILE("unref_delete()", " ", TAU_USER);
+  TAU_PROFILE("void unref_delete(RefCountType *)", " ", TAU_USER);
   // Although it may be tempting to try to upcast ptr to a
   // Although it may be tempting to try to upcast ptr to a
   // ReferenceCount object (particularly to get around inheritance
   // ReferenceCount object (particularly to get around inheritance
   // issues), resist that temptation, since some classes (in
   // issues), resist that temptation, since some classes (in
   // particular, TransformState and RenderState) rely on a non-virtual
   // particular, TransformState and RenderState) rely on a non-virtual
   // overloading of the unref() method.
   // overloading of the unref() method.
+
   if (!ptr->unref()) {
   if (!ptr->unref()) {
-#ifndef NDEBUG
-    if (get_leak_memory()) {
-      // In leak-memory mode, we don't actually delete the pointer,
-      // although we do call the destructor explicitly.  This has
-      // exactly the same effect as deleting it, without actually
-      // freeing up the memory it uses.
-
-      // Furthermore, if we have never-destruct set, we don't even
-      // call the destructor.
-      if (!get_never_destruct()) {
-        ptr->~RefCountType();
-      }
+    // The reference count has reached 0; time to delete the pointer.
+
+    // Is there a deletor in effect?  If so, pass it to that object.
+    ObjectDeletor *deletor = ObjectDeletor::get_global_ptr();
+    if (deletor != (ObjectDeletor *)NULL) {
+      // Re-ref the pointer before adding it to the deletor.  The
+      // deletor will de-ref it again before deleting it.
+      ptr->ref();
+      deletor->delete_object(RefCountDeleteWrapper<RefCountType>::do_delete, (void *)ptr);
       return;
       return;
     }
     }
-#endif
+
+    // If there's no deletor, just delete the pointer now.
     delete ptr;
     delete ptr;
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: defer_delete
+//  Description: This global helper function is designed to be called
+//               in lieu of the delete operator on any type of object
+//               (except for an object that inherits from
+//               ReferenceCount, of course, since you should never
+//               call delete explicitly on a reference-counted
+//               object).
+//
+//               This function will ultimately have the same effect as
+//               calling delete on the object.  If a ObjectDeletor
+//               is currently in effect, the delete will happen at
+//               some later time; but if a ObjectDeletor is not in
+//               effect, the delete will happen immediately.
+//
+//               You should not attempt to replace an array-delete
+//               call, e.g. delete[] array, with a call to
+//               defer_delete().  This only replaces single-object
+//               deletes, e.g. delete object.
+////////////////////////////////////////////////////////////////////
+template<class ObjectType>
+INLINE void
+defer_delete(ObjectType *ptr) {
+  TAU_PROFILE("void defer_delete(ObjectType *)", " ", TAU_USER);
+
+  ObjectDeletor *deletor = ObjectDeletor::get_global_ptr();
+  if (deletor != (ObjectDeletor *)NULL) {
+    deletor->delete_object(DeleteWrapper<ObjectType>::do_delete, (void *)ptr);
+    return;
+  }
+
+  // If there's no deletor, just delete the pointer now.
+  delete ptr;
+}
+
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: RefCountProxy::Constructor
 //     Function: RefCountProxy::Constructor

+ 16 - 0
panda/src/express/referenceCount.h

@@ -27,6 +27,7 @@
 #include "atomicAdjust.h"
 #include "atomicAdjust.h"
 #include "numeric_types.h"
 #include "numeric_types.h"
 #include "deletedChain.h"
 #include "deletedChain.h"
+#include "objectDeletor.h"
 
 
 #include <stdlib.h>
 #include <stdlib.h>
 
 
@@ -101,9 +102,24 @@ private:
   static TypeHandle _type_handle;
   static TypeHandle _type_handle;
 };
 };
 
 
+template<class ObjectType>
+class DeleteWrapper {
+public:
+  static void do_delete(void *ptr);
+};
+
+template<class RefCountType>
+class RefCountDeleteWrapper {
+public:
+  static void do_delete(void *ptr);
+};
+
 template<class RefCountType>
 template<class RefCountType>
 INLINE void unref_delete(RefCountType *ptr);
 INLINE void unref_delete(RefCountType *ptr);
 
 
+template<class ObjectType>
+INLINE void defer_delete(ObjectType *ptr);
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : RefCountProxy
 //       Class : RefCountProxy
 // Description : A "proxy" to use to make a reference-countable object
 // Description : A "proxy" to use to make a reference-countable object

+ 31 - 16
panda/src/pgraph/renderState.cxx

@@ -606,26 +606,41 @@ get_override(TypeHandle type) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool RenderState::
 bool RenderState::
 unref() const {
 unref() const {
-  ReMutexHolder holder(*_states_lock);
-
+  // Most of the time, we won't need to grab the lock.  We first check
+  // whether we think we will need to grab it.  Then, after we have
+  // successfully acquired the lock, we check that the condition is
+  // still valid.
+
+  // It is possible that, due to some race condition, this condition
+  // is never seen as true on any one thread.  In that case, the cycle
+  // will not automatically be detected and broken.  But since (a)
+  // that will be a relatively rare situation, (b) it will be
+  // expensive to protect against it, and (c) the damage is minimal,
+  // the race condition is allowed to remain.
   if (get_cache_ref_count() > 0 &&
   if (get_cache_ref_count() > 0 &&
       get_ref_count() == get_cache_ref_count() + 1) {
       get_ref_count() == get_cache_ref_count() + 1) {
-    // If we are about to remove the one reference that is not in the
-    // cache, leaving only references in the cache, then we need to
-    // check for a cycle involving this RenderState and break it if
-    // it exists.
 
 
-    if (auto_break_cycles) {
-      ++_last_cycle_detect;
-      if (r_detect_cycles(this, this, 1, _last_cycle_detect, NULL)) {
-        // Ok, we have a cycle.  This will be a leak unless we break the
-        // cycle by freeing the cache on this object.
-        if (pgraph_cat.is_debug()) {
-          pgraph_cat.debug()
-            << "Breaking cycle involving " << (*this) << "\n";
+    ReMutexHolder holder(*_states_lock);
+
+    if (get_cache_ref_count() > 0 &&
+        get_ref_count() == get_cache_ref_count() + 1) {
+      // If we are about to remove the one reference that is not in the
+      // cache, leaving only references in the cache, then we need to
+      // check for a cycle involving this RenderState and break it if
+      // it exists.
+      
+      if (auto_break_cycles) {
+        ++_last_cycle_detect;
+        if (r_detect_cycles(this, this, 1, _last_cycle_detect, NULL)) {
+          // Ok, we have a cycle.  This will be a leak unless we break the
+          // cycle by freeing the cache on this object.
+          if (pgraph_cat.is_debug()) {
+            pgraph_cat.debug()
+              << "Breaking cycle involving " << (*this) << "\n";
+          }
+          
+          ((RenderState *)this)->remove_cache_pointers();
         }
         }
-
-        ((RenderState *)this)->remove_cache_pointers();
       }
       }
     }
     }
   }
   }

+ 31 - 16
panda/src/pgraph/transformState.cxx

@@ -805,26 +805,41 @@ invert_compose(const TransformState *other) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool TransformState::
 bool TransformState::
 unref() const {
 unref() const {
-  ReMutexHolder holder(*_states_lock);
-
+  // Most of the time, we won't need to grab the lock.  We first check
+  // whether we think we will need to grab it.  Then, after we have
+  // successfully acquired the lock, we check that the condition is
+  // still valid.
+
+  // It is possible that, due to some race condition, this condition
+  // is never seen as true on any one thread.  In that case, the cycle
+  // will not automatically be detected and broken.  But since (a)
+  // that will be a relatively rare situation, (b) it will be
+  // expensive to protect against it, and (c) the damage is minimal,
+  // the race condition is allowed to remain.
   if (get_cache_ref_count() > 0 &&
   if (get_cache_ref_count() > 0 &&
       get_ref_count() == get_cache_ref_count() + 1) {
       get_ref_count() == get_cache_ref_count() + 1) {
-    // If we are about to remove the one reference that is not in the
-    // cache, leaving only references in the cache, then we need to
-    // check for a cycle involving this TransformState and break it if
-    // it exists.
 
 
-    if (auto_break_cycles) {
-      ++_last_cycle_detect;
-      if (r_detect_cycles(this, this, 1, _last_cycle_detect, NULL)) {
-        // Ok, we have a cycle.  This will be a leak unless we break the
-        // cycle by freeing the cache on this object.
-        if (pgraph_cat.is_debug()) {
-          pgraph_cat.debug()
-            << "Breaking cycle involving " << (*this) << "\n";
-        }
+    ReMutexHolder holder(*_states_lock);
 
 
-        ((TransformState *)this)->remove_cache_pointers();
+    if (get_cache_ref_count() > 0 &&
+        get_ref_count() == get_cache_ref_count() + 1) {
+      // If we are about to remove the one reference that is not in the
+      // cache, leaving only references in the cache, then we need to
+      // check for a cycle involving this TransformState and break it if
+      // it exists.
+      
+      if (auto_break_cycles) {
+        ++_last_cycle_detect;
+        if (r_detect_cycles(this, this, 1, _last_cycle_detect, NULL)) {
+          // Ok, we have a cycle.  This will be a leak unless we break the
+          // cycle by freeing the cache on this object.
+          if (pgraph_cat.is_debug()) {
+            pgraph_cat.debug()
+              << "Breaking cycle involving " << (*this) << "\n";
+          }
+          
+          ((TransformState *)this)->remove_cache_pointers();
+        }
       }
       }
     }
     }
   }
   }

+ 6 - 0
panda/src/putil/Sources.pp

@@ -27,6 +27,7 @@
     config_util.N config_util.h configurable.h \
     config_util.N config_util.h configurable.h \
     datagramInputFile.I datagramInputFile.h \
     datagramInputFile.I datagramInputFile.h \
     datagramOutputFile.I datagramOutputFile.h \
     datagramOutputFile.I datagramOutputFile.h \
+    deferredDeletor.h \
     drawMask.h \
     drawMask.h \
     factoryBase.I factoryBase.h \
     factoryBase.I factoryBase.h \
     factoryParam.I factoryParam.h factoryParams.I \
     factoryParam.I factoryParam.h factoryParams.I \
@@ -47,6 +48,7 @@
     nodeCachedReferenceCount.h nodeCachedReferenceCount.I \
     nodeCachedReferenceCount.h nodeCachedReferenceCount.I \
     nodePointerToBase.h nodePointerToBase.I \
     nodePointerToBase.h nodePointerToBase.I \
     nodePointerTo.h nodePointerTo.I \
     nodePointerTo.h nodePointerTo.I \
+    nonDeletor.h \
     pta_double.h \
     pta_double.h \
     pta_float.h pta_int.h \
     pta_float.h pta_int.h \
     string_utils.I string_utils.N string_utils.h \
     string_utils.I string_utils.N string_utils.h \
@@ -72,6 +74,7 @@
     clockObject.cxx \
     clockObject.cxx \
     config_util.cxx configurable.cxx \
     config_util.cxx configurable.cxx \
     datagramInputFile.cxx datagramOutputFile.cxx \
     datagramInputFile.cxx datagramOutputFile.cxx \
+    deferredDeletor.cxx \
     factoryBase.cxx \
     factoryBase.cxx \
     factoryParam.cxx factoryParams.cxx \
     factoryParam.cxx factoryParams.cxx \
     globalPointerRegistry.cxx \
     globalPointerRegistry.cxx \
@@ -85,6 +88,7 @@
     nodeCachedReferenceCount.cxx \
     nodeCachedReferenceCount.cxx \
     nodePointerToBase.cxx \
     nodePointerToBase.cxx \
     nodePointerTo.cxx \
     nodePointerTo.cxx \
+    nonDeletor.cxx \
     pta_double.cxx pta_float.cxx \
     pta_double.cxx pta_float.cxx \
     pta_int.cxx pta_ushort.cxx \
     pta_int.cxx pta_ushort.cxx \
     string_utils.cxx timedCycle.cxx typedWritable.cxx \
     string_utils.cxx timedCycle.cxx typedWritable.cxx \
@@ -112,6 +116,7 @@
     config_util.h configurable.h factory.I factory.h \
     config_util.h configurable.h factory.I factory.h \
     datagramInputFile.I datagramInputFile.h \
     datagramInputFile.I datagramInputFile.h \
     datagramOutputFile.I datagramOutputFile.h \
     datagramOutputFile.I datagramOutputFile.h \
+    deferredDeletor.h \
     drawMask.h \
     drawMask.h \
     factoryBase.I factoryBase.h factoryParam.I factoryParam.h \
     factoryBase.I factoryBase.h factoryParam.I factoryParam.h \
     factoryParams.I factoryParams.h \
     factoryParams.I factoryParams.h \
@@ -132,6 +137,7 @@
     nodeCachedReferenceCount.h nodeCachedReferenceCount.I \
     nodeCachedReferenceCount.h nodeCachedReferenceCount.I \
     nodePointerToBase.h nodePointerToBase.I \
     nodePointerToBase.h nodePointerToBase.I \
     nodePointerTo.h nodePointerTo.I \
     nodePointerTo.h nodePointerTo.I \
+    nonDeletor.h \
     pta_double.h \
     pta_double.h \
     pta_float.h pta_int.h pta_ushort.h \
     pta_float.h pta_int.h pta_ushort.h \
     string_utils.I \
     string_utils.I \

+ 4 - 13
panda/src/putil/cachedTypedWritableReferenceCount.I

@@ -234,21 +234,12 @@ INLINE void
 cache_unref_delete(RefCountType *ptr) {
 cache_unref_delete(RefCountType *ptr) {
   ptr->cache_unref();
   ptr->cache_unref();
   if (ptr->get_ref_count() == 0) {
   if (ptr->get_ref_count() == 0) {
-#ifndef NDEBUG
-    if (get_leak_memory()) {
-      // In leak-memory mode, we don't actually delete the pointer,
-      // although we do call the destructor explicitly.  This has
-      // exactly the same effect as deleting it, without actually
-      // freeing up the memory it uses.
-
-      // Furthermore, if we have never-destruct set, we don't even
-      // call the destructor.
-      if (!get_never_destruct()) {
-        ptr->~RefCountType();
-      }
+    ObjectDeletor *deletor = ObjectDeletor::get_global_ptr();
+    if (deletor != (ObjectDeletor *)NULL) {
+      ptr->ref();
+      deletor->delete_object(RefCountDeleteWrapper<RefCountType>::do_delete, (void *)ptr);
       return;
       return;
     }
     }
-#endif
     delete ptr;
     delete ptr;
   }
   }
 }
 }

+ 1 - 0
panda/src/putil/cachedTypedWritableReferenceCount.h

@@ -22,6 +22,7 @@
 #include "pandabase.h"
 #include "pandabase.h"
 
 
 #include "typedWritableReferenceCount.h"
 #include "typedWritableReferenceCount.h"
+#include "objectDeletor.h"
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : CachedTypedWritableReferenceCount
 //       Class : CachedTypedWritableReferenceCount

+ 5 - 0
panda/src/putil/config_util.cxx

@@ -38,6 +38,8 @@
 #include "writableParam.h"
 #include "writableParam.h"
 #include "keyboardButton.h"
 #include "keyboardButton.h"
 #include "mouseButton.h"
 #include "mouseButton.h"
+#include "deferredDeletor.h"
+#include "nonDeletor.h"
 
 
 #include "dconfig.h"
 #include "dconfig.h"
 
 
@@ -98,6 +100,9 @@ ConfigureFn(config_util) {
   KeyboardButton::init_keyboard_buttons();
   KeyboardButton::init_keyboard_buttons();
   MouseButton::init_mouse_buttons();
   MouseButton::init_mouse_buttons();
 
 
+  DeferredDeletor::register_deletor();
+  NonDeletor::register_deletor();
+
   register_type(BamReader::_remove_flag, "remove");
   register_type(BamReader::_remove_flag, "remove");
 }
 }
 
 

+ 68 - 0
panda/src/putil/deferredDeletor.cxx

@@ -0,0 +1,68 @@
+// Filename: deferredDeletor.cxx
+// Created by:  drose (10Apr06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "deferredDeletor.h"
+#include "config_util.h"
+#include "mutexHolder.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: DeferredDeletor::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+DeferredDeletor::
+DeferredDeletor() : _lock("DeferredDeletor") {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DeferredDeletor::delete_object
+//       Access: Public, Virtual
+//  Description: Adds the pointer to the object to be deleted, along
+//               with a pointer to a function that can delete it.
+////////////////////////////////////////////////////////////////////
+void DeferredDeletor::
+delete_object(DeleteFunc *func, void *ptr) {
+  MutexHolder holder(_lock);
+  _tokens.push_back(DeleteToken(func, ptr));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DeferredDeletor::flush
+//       Access: Public, Virtual
+//  Description: Ensures that any objects queued up for deletion have
+//               been fully deleted by the time flush() returns.
+////////////////////////////////////////////////////////////////////
+void DeferredDeletor::
+flush() {
+  MutexHolder holder(_lock);
+  Tokens::iterator ti;
+  for (ti = _tokens.begin(); ti != _tokens.end(); ++ti) {
+    (*ti).do_delete();
+  }
+  _tokens.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DeferredDeletor::register_deletor
+//       Access: Public, Static
+//  Description: Registers this deletor with the global pool.
+////////////////////////////////////////////////////////////////////
+void DeferredDeletor::
+register_deletor() {
+  register_subclass(new DeferredDeletor, "deferred");
+}

+ 48 - 0
panda/src/putil/deferredDeletor.h

@@ -0,0 +1,48 @@
+// Filename: deferredDeletor.h
+// Created by:  drose (10Apr06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef DEFERREDDELETOR_H
+#define DEFERREDDELETOR_H
+
+#include "pandabase.h"
+#include "objectDeletor.h"
+#include "pvector.h"
+#include "pmutex.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : DeferredDeletor
+// Description : The DeferredDeletor does all its deleting between
+//               frames, where it can be observed by PStats and where
+//               it won't interfere with rendering.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDAEXPRESS DeferredDeletor : public ObjectDeletor {
+public:
+  DeferredDeletor();
+
+  virtual void delete_object(DeleteFunc *func, void *ptr);
+  virtual void flush();
+
+  static void register_deletor();
+
+private:
+  typedef pvector<DeleteToken> Tokens;
+  Tokens _tokens;
+  Mutex _lock;
+};
+
+#endif

+ 5 - 13
panda/src/putil/nodeCachedReferenceCount.I

@@ -258,21 +258,13 @@ INLINE void
 node_unref_delete(RefCountType *ptr) {
 node_unref_delete(RefCountType *ptr) {
   ptr->node_unref();
   ptr->node_unref();
   if (ptr->get_ref_count() == 0) {
   if (ptr->get_ref_count() == 0) {
-#ifndef NDEBUG
-    if (get_leak_memory()) {
-      // In leak-memory mode, we don't actually delete the pointer,
-      // although we do call the destructor explicitly.  This has
-      // exactly the same effect as deleting it, without actually
-      // freeing up the memory it uses.
-
-      // Furthermore, if we have never-destruct set, we don't even
-      // call the destructor.
-      if (!get_never_destruct()) {
-        ptr->~RefCountType();
-      }
+    ObjectDeletor *deletor = ObjectDeletor::get_global_ptr();
+    if (deletor != (ObjectDeletor *)NULL) {
+      ptr->ref();
+      deletor->delete_object(RefCountDeleteWrapper<RefCountType>::do_delete, (void *)ptr);
       return;
       return;
     }
     }
-#endif
+
     delete ptr;
     delete ptr;
   }
   }
 }
 }

+ 1 - 0
panda/src/putil/nodeCachedReferenceCount.h

@@ -22,6 +22,7 @@
 #include "pandabase.h"
 #include "pandabase.h"
 
 
 #include "cachedTypedWritableReferenceCount.h"
 #include "cachedTypedWritableReferenceCount.h"
+#include "objectDeletor.h"
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : NodeCachedReferenceCount
 //       Class : NodeCachedReferenceCount

+ 44 - 0
panda/src/putil/nonDeletor.cxx

@@ -0,0 +1,44 @@
+// Filename: nonDeletor.cxx
+// Created by:  drose (10Apr06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "nonDeletor.h"
+#include "config_util.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: NonDeletor::delete_object
+//       Access: Public, Virtual
+//  Description: Adds the pointer to the object to be deleted, along
+//               with a pointer to a function that can delete it.
+////////////////////////////////////////////////////////////////////
+void NonDeletor::
+delete_object(DeleteFunc *, void *ptr) {
+  if (util_cat.is_spam()) {
+    util_cat.spam()
+      << "Not deleting pointer " << ptr << "\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NonDeletor::register_deletor
+//       Access: Public, Static
+//  Description: Registers this deletor with the global pool.
+////////////////////////////////////////////////////////////////////
+void NonDeletor::
+register_deletor() {
+  register_subclass(new NonDeletor, "none");
+}

+ 40 - 0
panda/src/putil/nonDeletor.h

@@ -0,0 +1,40 @@
+// Filename: nonDeletor.h
+// Created by:  drose (10Apr06)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef NONDELETOR_H
+#define NONDELETOR_H
+
+#include "pandabase.h"
+#include "objectDeletor.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : NonDeletor
+// Description : This specialization of ObjectDeletor serves a very
+//               specific function: it *doesn't* delete pointers it is
+//               given.  This is useful mainly for testing, for
+//               instance to determine if there is a problem with a
+//               destructor somewhere.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDAEXPRESS NonDeletor : public ObjectDeletor {
+public:
+  virtual void delete_object(DeleteFunc *func, void *ptr);
+
+  static void register_deletor();
+};
+
+#endif

+ 1 - 0
panda/src/putil/putil_composite1.cxx

@@ -14,6 +14,7 @@
 #include "configurable.cxx"
 #include "configurable.cxx"
 #include "datagramInputFile.cxx"
 #include "datagramInputFile.cxx"
 #include "datagramOutputFile.cxx"
 #include "datagramOutputFile.cxx"
+#include "deferredDeletor.cxx"
 #include "factoryBase.cxx"
 #include "factoryBase.cxx"
 #include "factoryParam.cxx"
 #include "factoryParam.cxx"
 #include "factoryParams.cxx"
 #include "factoryParams.cxx"

+ 1 - 0
panda/src/putil/putil_composite2.cxx

@@ -5,6 +5,7 @@
 #include "nodeCachedReferenceCount.cxx"
 #include "nodeCachedReferenceCount.cxx"
 #include "nodePointerToBase.cxx"
 #include "nodePointerToBase.cxx"
 #include "nodePointerTo.cxx"
 #include "nodePointerTo.cxx"
+#include "nonDeletor.cxx"
 #include "pta_double.cxx"
 #include "pta_double.cxx"
 #include "pta_float.cxx"
 #include "pta_float.cxx"
 #include "pta_int.cxx"
 #include "pta_int.cxx"