Browse Source

NeverFreeMemory

David Rose 18 years ago
parent
commit
5f1b029f42

+ 3 - 0
dtool/src/dtoolbase/Sources.pp

@@ -26,6 +26,7 @@
     mutexWin32Impl.h mutexWin32Impl.I \
     mutexWin32Impl.h mutexWin32Impl.I \
     mutexSpinlockImpl.h mutexSpinlockImpl.I \
     mutexSpinlockImpl.h mutexSpinlockImpl.I \
     nearly_zero.h \
     nearly_zero.h \
+    neverFreeMemory.h neverFreeMemory.I \
     numeric_types.h \
     numeric_types.h \
     register_type.I register_type.h \
     register_type.I register_type.h \
     selectThreadImpl.h \
     selectThreadImpl.h \
@@ -50,6 +51,7 @@
     mutexPosixImpl.cxx \
     mutexPosixImpl.cxx \
     mutexWin32Impl.cxx \
     mutexWin32Impl.cxx \
     mutexSpinlockImpl.cxx \
     mutexSpinlockImpl.cxx \
+    neverFreeMemory.cxx \
     register_type.cxx \
     register_type.cxx \
     typeHandle.cxx \
     typeHandle.cxx \
     typeRegistry.cxx typeRegistryNode.cxx \
     typeRegistry.cxx typeRegistryNode.cxx \
@@ -74,6 +76,7 @@
     mutexWin32Impl.h mutexWin32Impl.I \
     mutexWin32Impl.h mutexWin32Impl.I \
     mutexSpinlockImpl.h mutexSpinlockImpl.I \
     mutexSpinlockImpl.h mutexSpinlockImpl.I \
     nearly_zero.h \
     nearly_zero.h \
+    neverFreeMemory.h neverFreeMemory.I \
     numeric_types.h \
     numeric_types.h \
     register_type.I register_type.h \
     register_type.I register_type.h \
     selectThreadImpl.h \
     selectThreadImpl.h \

+ 60 - 17
dtool/src/dtoolbase/deletedChain.T

@@ -42,19 +42,24 @@ allocate(size_t size, TypeHandle type_handle) {
   init_lock();
   init_lock();
   _lock->lock();
   _lock->lock();
   if (_deleted_chain != (ObjectNode *)NULL) {
   if (_deleted_chain != (ObjectNode *)NULL) {
-#ifdef DO_MEMORY_USAGE
-    type_handle.dec_memory_usage(TypeHandle::MC_deleted_chain_inactive, alloc_size);
-    type_handle.inc_memory_usage(TypeHandle::MC_deleted_chain_active, alloc_size);
-#endif  // DO_MEMORY_USAGE
-
     obj = _deleted_chain;
     obj = _deleted_chain;
     _deleted_chain = _deleted_chain->_next;
     _deleted_chain = _deleted_chain->_next;
     _lock->release();
     _lock->release();
+
 #ifdef USE_DELETEDCHAINFLAG
 #ifdef USE_DELETEDCHAINFLAG
     assert(obj->_flag == (PN_int32)DCF_deleted);
     assert(obj->_flag == (PN_int32)DCF_deleted);
     obj->_flag = DCF_alive;
     obj->_flag = DCF_alive;
 #endif  // NDEBUG
 #endif  // NDEBUG
-    return node_to_type(obj);
+
+    Type *ptr = node_to_type(obj);
+
+#ifdef DO_MEMORY_USAGE
+    type_handle.dec_memory_usage(TypeHandle::MC_deleted_chain_inactive, alloc_size);
+    type_handle.inc_memory_usage(TypeHandle::MC_deleted_chain_active, alloc_size);
+    (*global_mark_pointer)(ptr, max(size, sizeof(ObjectNode)), make_ref_ptr(ptr));
+#endif  // DO_MEMORY_USAGE
+
+    return ptr;
   }
   }
   _lock->release();
   _lock->release();
 
 
@@ -84,21 +89,21 @@ allocate(size_t size, TypeHandle type_handle) {
   // If we get here, the deleted_chain is empty; we have to allocate a
   // If we get here, the deleted_chain is empty; we have to allocate a
   // new object from the system pool.
   // new object from the system pool.
 
 
-#ifdef DO_MEMORY_USAGE
-  type_handle.inc_memory_usage(TypeHandle::MC_deleted_chain_active, alloc_size);
-#endif  // DO_MEMORY_USAGE
-  size = max(alloc_size, sizeof(ObjectNode));
+  alloc_size = max(alloc_size, sizeof(ObjectNode));
+  obj = (ObjectNode *)NeverFreeMemory::alloc(alloc_size);
 
 
-#ifdef DO_MEMORY_USAGE
-  obj = (ObjectNode *)(*global_operator_new)(alloc_size);
-#else
-  obj = (ObjectNode *)malloc(alloc_size);
-#endif
 #ifdef USE_DELETEDCHAINFLAG
 #ifdef USE_DELETEDCHAINFLAG
   obj->_flag = DCF_alive;
   obj->_flag = DCF_alive;
-#endif  // NDEBUG
+#endif  // USE_DELETEDCHAINFLAG
+
+  Type *ptr = node_to_type(obj);
 
 
-  return node_to_type(obj);
+#ifdef DO_MEMORY_USAGE
+  type_handle.inc_memory_usage(TypeHandle::MC_deleted_chain_active, alloc_size);
+  (*global_mark_pointer)(ptr, max(size, sizeof(ObjectNode)), make_ref_ptr(ptr));
+#endif  // DO_MEMORY_USAGE
+
+  return ptr;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -114,12 +119,16 @@ deallocate(Type *ptr, TypeHandle type_handle) {
 
 
 #ifdef DO_MEMORY_USAGE
 #ifdef DO_MEMORY_USAGE
   size_t alloc_size = sizeof(Type);
   size_t alloc_size = sizeof(Type);
+
+  (*global_mark_pointer)(ptr, 0, make_ref_ptr(ptr));
+
 #ifdef USE_DELETEDCHAINFLAG
 #ifdef USE_DELETEDCHAINFLAG
   // In development mode, we also need to reserve space for _flag.
   // In development mode, we also need to reserve space for _flag.
   alloc_size += sizeof(PN_int32);
   alloc_size += sizeof(PN_int32);
 #endif  // NDEBUG
 #endif  // NDEBUG
   type_handle.dec_memory_usage(TypeHandle::MC_deleted_chain_active, alloc_size);
   type_handle.dec_memory_usage(TypeHandle::MC_deleted_chain_active, alloc_size);
   type_handle.inc_memory_usage(TypeHandle::MC_deleted_chain_inactive, alloc_size);
   type_handle.inc_memory_usage(TypeHandle::MC_deleted_chain_inactive, alloc_size);
+
 #endif  // DO_MEMORY_USAGE
 #endif  // DO_MEMORY_USAGE
 
 
   TVOLATILE ObjectNode *obj = type_to_node(ptr);
   TVOLATILE ObjectNode *obj = type_to_node(ptr);
@@ -190,6 +199,40 @@ validate(const Type *ptr) {
 #endif  // NDEBUG
 #endif  // NDEBUG
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: DeletedChain::make_ref_ptr
+//       Access: Public, Static
+//  Description: This method has two overloads: one that accepts a
+//               void *, and one that accepts a ReferenceCount *.  We
+//               rely on the C++ compiler to select the most
+//               appropriate one for a given type to return the
+//               ReferenceCount pointer that corresponds to a
+//               particular type, or NULL if the type does not inherit
+//               from ReferenceCount.
+////////////////////////////////////////////////////////////////////
+template<class Type>
+INLINE ReferenceCount *DeletedChain<Type>::
+make_ref_ptr(void *) {
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DeletedChain::make_ref_ptr
+//       Access: Public, Static
+//  Description: This method has two overloads: one that accepts a
+//               void *, and one that accepts a ReferenceCount *.  We
+//               rely on the C++ compiler to select the most
+//               appropriate one for a given type to return the
+//               ReferenceCount pointer that corresponds to a
+//               particular type, or NULL if the type does not inherit
+//               from ReferenceCount.
+////////////////////////////////////////////////////////////////////
+template<class Type>
+INLINE ReferenceCount *DeletedChain<Type>::
+make_ref_ptr(ReferenceCount *ptr) {
+  return ptr;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: DeletedChain::node_to_type
 //     Function: DeletedChain::node_to_type
 //       Access: Private, Static
 //       Access: Private, Static

+ 4 - 1
dtool/src/dtoolbase/deletedChain.h

@@ -20,7 +20,7 @@
 #define DELETEDCHAIN_H
 #define DELETEDCHAIN_H
 
 
 #include "dtoolbase.h"
 #include "dtoolbase.h"
-
+#include "neverFreeMemory.h"
 #include "mutexImpl.h"
 #include "mutexImpl.h"
 #include "atomicAdjust.h"
 #include "atomicAdjust.h"
 #include "numeric_types.h"
 #include "numeric_types.h"
@@ -87,6 +87,9 @@ public:
 
 
   INLINE bool validate(const Type *ptr);
   INLINE bool validate(const Type *ptr);
 
 
+  static INLINE ReferenceCount *make_ref_ptr(void *ptr);
+  static INLINE ReferenceCount *make_ref_ptr(ReferenceCount *ptr);
+
 private:
 private:
   class ObjectNode {
   class ObjectNode {
   public:
   public:

+ 12 - 0
dtool/src/dtoolbase/dtoolbase.cxx

@@ -56,8 +56,12 @@ void default_operator_delete(void *ptr) {
   dlfree(ptr);
   dlfree(ptr);
 }
 }
 
 
+void default_mark_pointer(void *, size_t, ReferenceCount *) {
+}
+
 void *(*global_operator_new)(size_t size) = &default_operator_new;
 void *(*global_operator_new)(size_t size) = &default_operator_new;
 void (*global_operator_delete)(void *ptr) = &default_operator_delete;
 void (*global_operator_delete)(void *ptr) = &default_operator_delete;
+void (*global_mark_pointer)(void *ptr, size_t size, ReferenceCount *ref_ptr) = &default_mark_pointer;
 
 
 /////////////////////////////////////////////////////////////////////
 /////////////////////////////////////////////////////////////////////
 //
 //
@@ -93,8 +97,12 @@ void default_operator_delete(void *ptr) {
   dlfree(ptr);
   dlfree(ptr);
 }
 }
 
 
+void default_mark_pointer(void *, size_t, ReferenceCount *) {
+}
+
 void *(*global_operator_new)(size_t size) = &default_operator_new;
 void *(*global_operator_new)(size_t size) = &default_operator_new;
 void (*global_operator_delete)(void *ptr) = &default_operator_delete;
 void (*global_operator_delete)(void *ptr) = &default_operator_delete;
+void (*global_mark_pointer)(void *ptr, size_t size, ReferenceCount *ref_ptr) = &default_mark_pointer;
 
 
 /////////////////////////////////////////////////////////////////////
 /////////////////////////////////////////////////////////////////////
 //
 //
@@ -120,8 +128,12 @@ void default_operator_delete(void *ptr) {
   free(ptr);
   free(ptr);
 }
 }
 
 
+void default_mark_pointer(void *, size_t, ReferenceCount *) {
+}
+
 void *(*global_operator_new)(size_t size) = &default_operator_new;
 void *(*global_operator_new)(size_t size) = &default_operator_new;
 void (*global_operator_delete)(void *ptr) = &default_operator_delete;
 void (*global_operator_delete)(void *ptr) = &default_operator_delete;
+void (*global_mark_pointer)(void *ptr, size_t size, ReferenceCount *ref_ptr) = &default_mark_pointer;
 
 
 /////////////////////////////////////////////////////////////////////
 /////////////////////////////////////////////////////////////////////
 //
 //

+ 9 - 0
dtool/src/dtoolbase/dtoolbase_cc.h

@@ -137,15 +137,24 @@ typedef ios::seekdir ios_seekdir;
 
 
 #endif  // CPPPARSER
 #endif  // CPPPARSER
 
 
+// The ReferenceCount class is defined later, within Panda, but we
+// need to pass around forward references to it here at the very low
+// level.
+class ReferenceCount;
+
 // Now redefine global operators new and delete so we can optionally
 // Now redefine global operators new and delete so we can optionally
 // provide custom handlers for them.  The MemoryUsage class in Panda
 // provide custom handlers for them.  The MemoryUsage class in Panda
 // takes advantage of this to track the size of allocated pointers.
 // takes advantage of this to track the size of allocated pointers.
 #ifndef USE_MEMORY_NOWRAPPERS
 #ifndef USE_MEMORY_NOWRAPPERS
 EXPCL_DTOOL void *default_operator_new(size_t size);
 EXPCL_DTOOL void *default_operator_new(size_t size);
 EXPCL_DTOOL void default_operator_delete(void *ptr);
 EXPCL_DTOOL void default_operator_delete(void *ptr);
+EXPCL_DTOOL void default_mark_pointer(void *ptr, size_t orig_size,
+                                      ReferenceCount *ref_ptr);
 
 
 extern EXPCL_DTOOL void *(*global_operator_new)(size_t size);
 extern EXPCL_DTOOL void *(*global_operator_new)(size_t size);
 extern EXPCL_DTOOL void (*global_operator_delete)(void *ptr);
 extern EXPCL_DTOOL void (*global_operator_delete)(void *ptr);
+extern EXPCL_DTOOL void (*global_mark_pointer)(void *ptr, size_t size,
+                                               ReferenceCount *ref_ptr);
 
 
 #ifdef REDEFINE_GLOBAL_OPERATOR_NEW
 #ifdef REDEFINE_GLOBAL_OPERATOR_NEW
 #ifdef GLOBAL_OPERATOR_NEW_EXCEPTIONS
 #ifdef GLOBAL_OPERATOR_NEW_EXCEPTIONS

+ 1 - 0
dtool/src/dtoolbase/dtoolbase_composite1.cxx

@@ -5,3 +5,4 @@
 #include "atomicAdjustWin32Impl.cxx"
 #include "atomicAdjustWin32Impl.cxx"
 #include "dtoolbase.cxx"
 #include "dtoolbase.cxx"
 #include "memoryBase.cxx"
 #include "memoryBase.cxx"
+#include "mutexDummyImpl.cxx"

+ 1 - 1
dtool/src/dtoolbase/dtoolbase_composite2.cxx

@@ -1,7 +1,7 @@
-#include "mutexDummyImpl.cxx"
 #include "mutexPosixImpl.cxx"
 #include "mutexPosixImpl.cxx"
 #include "mutexWin32Impl.cxx"
 #include "mutexWin32Impl.cxx"
 #include "mutexSpinlockImpl.cxx"
 #include "mutexSpinlockImpl.cxx"
+#include "neverFreeMemory.cxx"
 #include "register_type.cxx"
 #include "register_type.cxx"
 #include "typeHandle.cxx"
 #include "typeHandle.cxx"
 #include "typeRegistry.cxx"
 #include "typeRegistry.cxx"

+ 117 - 0
dtool/src/dtoolbase/neverFreeMemory.I

@@ -0,0 +1,117 @@
+// Filename: neverFreeMemory.I
+// Created by:  drose (14Jun07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: NeverFreeMemory::alloc
+//       Access: Public, Static
+//  Description: Returns a pointer to a newly-allocated block of
+//               memory of the indicated size.
+////////////////////////////////////////////////////////////////////
+INLINE void *NeverFreeMemory::
+alloc(size_t size) {
+  return get_global_ptr()->ns_alloc(size);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NeverFreeMemory::get_total_alloc
+//       Access: Published, Static
+//  Description: Returns the total number of bytes consumed by all the
+//               pages allocated internally by this object.
+////////////////////////////////////////////////////////////////////
+INLINE size_t NeverFreeMemory::
+get_total_alloc() {
+  return get_global_ptr()->_total_alloc;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NeverFreeMemory::get_total_used
+//       Access: Published, Static
+//  Description: Returns the total number of bytes requested by the
+//               application in calls to NeverFreeMemory::alloc().
+////////////////////////////////////////////////////////////////////
+INLINE size_t NeverFreeMemory::
+get_total_used() {
+  return get_global_ptr()->_total_used;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NeverFreeMemory::get_total_unused
+//       Access: Published, Static
+//  Description: Returns the difference between get_total_alloc() and
+//               get_total_used().  This represents bytes in allocated
+//               pages that have not (yet) been used by the
+//               application.
+////////////////////////////////////////////////////////////////////
+INLINE size_t NeverFreeMemory::
+get_total_unused() {
+  NeverFreeMemory *global_ptr = get_global_ptr();
+  global_ptr->_lock.lock();
+  size_t total_unused = global_ptr->_total_alloc - global_ptr->_total_used;
+  global_ptr->_lock.release();
+  return total_unused;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NeverFreeMemory::get_global_ptr
+//       Access: Private, Static
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE NeverFreeMemory *NeverFreeMemory::
+get_global_ptr() {
+  if (_global_ptr == (NeverFreeMemory *)NULL) {
+    make_global_ptr();
+  }
+  return _global_ptr;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NeverFreeMemory::Page::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE NeverFreeMemory::Page::
+Page(void *start, size_t size) : 
+  _next((unsigned char *)start),
+  _remaining(size)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NeverFreeMemory::Page::operator <
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE bool NeverFreeMemory::Page::
+operator < (const NeverFreeMemory::Page &other) const {
+  return _remaining < other._remaining;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NeverFreeMemory::Page::alloc
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void *NeverFreeMemory::Page::
+alloc(size_t size) {
+  assert(size <= _remaining);
+  void *result = _next;
+  _next += size;
+  _remaining -= size;
+  return result;
+}

+ 141 - 0
dtool/src/dtoolbase/neverFreeMemory.cxx

@@ -0,0 +1,141 @@
+// Filename: neverFreeMemory.cxx
+// Created by:  drose (14Jun07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "neverFreeMemory.h"
+#include "atomicAdjust.h"
+
+#ifdef WIN32
+
+#else
+
+// Posix case.
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+
+#endif  // WIN32
+
+NeverFreeMemory *NeverFreeMemory::_global_ptr;
+
+// If a page has fewer than this many bytes remaining, never mind
+// about it.
+static const size_t min_page_remaining_size = 16;
+
+// We always allocate at least this many bytes at a time.
+static const size_t min_page_size = 128 * 1024;  // 128K
+
+////////////////////////////////////////////////////////////////////
+//     Function: NeverFreeMemory::Constructor
+//       Access: Private
+//  Description: 
+////////////////////////////////////////////////////////////////////
+NeverFreeMemory::
+NeverFreeMemory() {
+  _total_alloc = 0;
+  _total_used = 0;
+
+  _page_size = 1;
+
+#ifdef WIN32
+
+
+#else
+
+  // Posix case.
+  _page_size = getpagesize();
+
+#endif  // WIN32
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NeverFreeMemory::ns_alloc
+//       Access: Private
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void *NeverFreeMemory::
+ns_alloc(size_t size) {
+  _lock.lock();
+
+  _total_used += size;
+  
+  // Look for a page that has sufficient space remaining.
+
+  Pages::iterator pi = _pages.lower_bound(Page(NULL, size));
+  if (pi != _pages.end()) {
+    // Here's a page with enough remaining space.
+    Page page = (*pi);
+    _pages.erase(pi);
+    void *result = page.alloc(size);
+    if (page._remaining >= min_page_remaining_size) {
+      _pages.insert(page);
+    }
+    _lock.release();
+    return result;
+  }
+
+  // We have to allocate a new page.  Allocate at least min_page_size
+  // bytes, and then round that up to the next _page_size bytes.
+  size_t needed_size = max(size, min_page_size);
+  needed_size = ((needed_size + _page_size - 1) / _page_size) * _page_size;
+  void *start = NULL;
+
+#ifdef WIN32
+
+#else
+
+  // Posix case.
+  start = mmap(NULL, needed_size, PROT_READ | PROT_WRITE, 
+               MAP_PRIVATE | MAP_ANON, -1, 0);
+  if (start == (void *)-1) {
+    perror("mmap");
+    start = NULL;
+  }
+
+#endif  // WIN32
+
+  if (start == NULL) {
+    start = malloc(needed_size);
+  }
+
+  _total_alloc += needed_size;
+
+  Page page(start, needed_size);
+  void *result = page.alloc(size);
+  if (page._remaining >= min_page_remaining_size) {
+    _pages.insert(page);
+  }
+  _lock.release();
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NeverFreeMemory::make_global_ptr
+//       Access: Private, Static
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void NeverFreeMemory::
+make_global_ptr() {
+  NeverFreeMemory *ptr = new NeverFreeMemory;
+  void *result = AtomicAdjust::compare_and_exchange_ptr
+    ((void * TVOLATILE &)_global_ptr, (void *)NULL, (void *)ptr);
+  if (result != NULL) {
+    // Someone else got there first.
+    delete ptr;
+  }
+  assert(_global_ptr != (NeverFreeMemory *)NULL);
+}

+ 81 - 0
dtool/src/dtoolbase/neverFreeMemory.h

@@ -0,0 +1,81 @@
+// Filename: neverFreeMemory.h
+// Created by:  drose (14Jun07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 NEVERFREEMEMORY_H
+#define NEVERFREEMEMORY_H
+
+#include "dtoolbase.h"
+
+#include "mutexImpl.h"
+#include <set>
+
+////////////////////////////////////////////////////////////////////
+//       Class : NeverFreeMemory
+// Description : This class is used to allocate bytes of memory from a
+//               pool that is never intended to be freed.  It is
+//               particularly useful to support DeletedChain, which
+//               allocates memory in just such a fashion.
+//
+//               When it is known that memory will not be freed, it is
+//               preferable to use this instead of the standard
+//               malloc() (or global_operator_new()) call, since this
+//               will help reduce fragmentation problems in the
+//               dynamic heap.  Also, memory allocated from here will
+//               exhibit less wasted space.
+////////////////////////////////////////////////////////////////////
+class EXPCL_DTOOL NeverFreeMemory {
+private:
+  NeverFreeMemory();
+
+public:
+  INLINE static void *alloc(size_t size);
+
+PUBLISHED:
+  INLINE static size_t get_total_alloc();
+  INLINE static size_t get_total_used();
+  INLINE static size_t get_total_unused();
+
+private:
+  void *ns_alloc(size_t size);
+  INLINE static NeverFreeMemory *get_global_ptr();
+  static void make_global_ptr();
+
+private:
+  class Page {
+  public:
+    INLINE Page(void *start, size_t size);
+    INLINE bool operator < (const Page &other) const;
+    INLINE void *alloc(size_t size);
+
+    unsigned char *_next;
+    size_t _remaining;
+  };
+
+  typedef set<Page> Pages;
+  Pages _pages;
+
+  size_t _page_size;
+  size_t _total_alloc;
+  size_t _total_used;
+  MutexImpl _lock;
+  static NeverFreeMemory * TVOLATILE _global_ptr;
+};
+
+#include "neverFreeMemory.I"
+
+#endif

+ 1 - 0
panda/src/express/config_express.N

@@ -12,6 +12,7 @@ forcetype TypeHandle
 forcetype TypeRegistry
 forcetype TypeRegistry
 forcetype StreamReader
 forcetype StreamReader
 forcetype StreamWriter
 forcetype StreamWriter
+forcetype NeverFreeMemory
 
 
 forcetype ConfigExpress
 forcetype ConfigExpress
 renametype ConfigExpress ConfigExpress
 renametype ConfigExpress ConfigExpress

+ 0 - 5
panda/src/express/dcast.cxx

@@ -39,11 +39,6 @@ _dcast_verify(TypeHandle want_handle, size_t want_size,
     if (ptr == (const TypedObject *)NULL) {
     if (ptr == (const TypedObject *)NULL) {
       // This is allowed these days.  It used to be an error, but
       // This is allowed these days.  It used to be an error, but
       // what the heck.
       // what the heck.
-      if (express_cat->is_debug()) {
-        express_cat->debug()
-          << "Attempt to cast NULL pointer to " 
-          << want_handle << "\n";
-      }
       return true;
       return true;
     }
     }
 #if defined(_DEBUG) && defined(_WIN32)
 #if defined(_DEBUG) && defined(_WIN32)

+ 10 - 1
panda/src/express/memoryInfo.I

@@ -24,7 +24,16 @@
 //               should always be non-NULL.
 //               should always be non-NULL.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void *MemoryInfo::get_void_ptr() const {
 void *MemoryInfo::get_void_ptr() const {
-  return _void_ptr;
+  if (_void_ptr != (void *)NULL) {
+    return _void_ptr;
+  }
+  if (_ref_ptr == (void *)NULL) {
+    return _typed_ptr;
+  }
+  if (_typed_ptr == (void *)NULL) {
+    return _ref_ptr;
+  }
+  return ((void *)_ref_ptr < (void *)_typed_ptr) ? (void *)_ref_ptr : (void *)_typed_ptr;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 4 - 4
panda/src/express/memoryInfo.cxx

@@ -72,7 +72,7 @@ get_type() {
   if (type != _static_type) {
   if (type != _static_type) {
     if (express_cat.is_spam()) {
     if (express_cat.is_spam()) {
       express_cat.spam()
       express_cat.spam()
-        << "Pointer " << (void *)_ref_ptr << " has static type "
+        << "Pointer " << get_void_ptr() << " has static type "
         << _static_type << " and dynamic type " << _dynamic_type << "\n";
         << _static_type << " and dynamic type " << _dynamic_type << "\n";
     }
     }
   }
   }
@@ -127,14 +127,14 @@ determine_dynamic_type() {
         if (orig_type != _dynamic_type) {
         if (orig_type != _dynamic_type) {
           if (express_cat.is_spam()) {
           if (express_cat.is_spam()) {
             express_cat.spam()
             express_cat.spam()
-              << "Updating " << (void *)_ref_ptr << " from type "
+              << "Updating " << get_void_ptr() << " from type "
               << orig_type << " to type " << _dynamic_type << "\n";
               << orig_type << " to type " << _dynamic_type << "\n";
           }
           }
         }
         }
 
 
       } else {
       } else {
         express_cat.warning()
         express_cat.warning()
-          << "Pointer " << (void *)_ref_ptr << " previously indicated as type "
+          << "Pointer " << get_void_ptr() << " previously indicated as type "
           << orig_type << " is now type " << got_type << "!\n";
           << orig_type << " is now type " << got_type << "!\n";
       }
       }
     }    
     }    
@@ -155,7 +155,7 @@ bool MemoryInfo::
 update_type_handle(TypeHandle &destination, TypeHandle refined) {
 update_type_handle(TypeHandle &destination, TypeHandle refined) {
   if (refined == TypeHandle::none()) {
   if (refined == TypeHandle::none()) {
     express_cat.error()
     express_cat.error()
-      << "Attempt to update type of " << (void *)_ref_ptr
+      << "Attempt to update type of " << get_void_ptr()
       << "(type is " << get_type()
       << "(type is " << get_type()
       << ") to an undefined type!\n";
       << ") to an undefined type!\n";
 
 

+ 2 - 4
panda/src/express/memoryInfo.h

@@ -62,10 +62,8 @@ private:
 
 
 private:
 private:
   enum Flags {
   enum Flags {
-    F_got_ref                 = 0x0001,
-    F_got_void                = 0x0002,
-    F_size_known              = 0x0004,
-    F_reconsider_dynamic_type = 0x0008,
+    F_size_known              = 0x0001,
+    F_reconsider_dynamic_type = 0x0002,
   };
   };
   
   
   void *_void_ptr;
   void *_void_ptr;

+ 220 - 125
panda/src/express/memoryUsage.cxx

@@ -59,7 +59,7 @@ double MemoryUsage::AgeHistogram::_cutoff[MemoryUsage::AgeHistogram::num_buckets
 //  Description: Adds a single entry to the histogram.
 //  Description: Adds a single entry to the histogram.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void MemoryUsage::TypeHistogram::
 void MemoryUsage::TypeHistogram::
-add_info(TypeHandle type, MemoryInfo &info) {
+add_info(TypeHandle type, MemoryInfo *info) {
   _counts[type].add_info(info);
   _counts[type].add_info(info);
 }
 }
 
 
@@ -138,7 +138,7 @@ AgeHistogram() {
 //  Description: Adds a single entry to the histogram.
 //  Description: Adds a single entry to the histogram.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void MemoryUsage::AgeHistogram::
 void MemoryUsage::AgeHistogram::
-add_info(double age, MemoryInfo &info) {
+add_info(double age, MemoryInfo *info) {
   int bucket = choose_bucket(age);
   int bucket = choose_bucket(age);
   nassertv(bucket >= 0 && bucket < num_buckets);
   nassertv(bucket >= 0 && bucket < num_buckets);
   _counts[bucket].add_info(info);
   _counts[bucket].add_info(info);
@@ -215,6 +215,14 @@ operator_new_handler(size_t size) {
     MemoryUsage *mu = get_global_ptr();
     MemoryUsage *mu = get_global_ptr();
     if (mu->_track_memory_usage) {
     if (mu->_track_memory_usage) {
       ptr = default_operator_new(size);
       ptr = default_operator_new(size);
+      /*
+      if (express_cat.is_spam()) {
+        express_cat.spam()
+          << "Allocating pointer " << (void *)ptr
+          << " of size " << size << ".\n";
+      }
+      */
+
       get_global_ptr()->ns_record_void_pointer(ptr, size);
       get_global_ptr()->ns_record_void_pointer(ptr, size);
 
 
     } else {
     } else {
@@ -249,6 +257,12 @@ operator_delete_handler(void *ptr) {
   } else {
   } else {
     MemoryUsage *mu = get_global_ptr();
     MemoryUsage *mu = get_global_ptr();
     if (mu->_track_memory_usage) {
     if (mu->_track_memory_usage) {
+      /*
+      if (express_cat.is_spam()) {
+        express_cat.spam()
+          << "Removing pointer " << (void *)ptr << "\n";
+      }
+      */
       mu->ns_remove_void_pointer(ptr);
       mu->ns_remove_void_pointer(ptr);
       default_operator_delete(ptr);
       default_operator_delete(ptr);
     } else {
     } else {
@@ -259,6 +273,24 @@ operator_delete_handler(void *ptr) {
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: MemoryUsage::mark_pointer_handler
+//       Access: Public, Static
+//  Description: This special function is similar to operator_new_handler,
+//               but instead of allocating new memory it simply marks
+//               existing memory as already having been allocated.
+////////////////////////////////////////////////////////////////////
+void MemoryUsage::
+mark_pointer_handler(void *ptr, size_t size, 
+                     ReferenceCount *ref_ptr) {
+  if (!_recursion_protect) {
+    MemoryUsage *mu = get_global_ptr();
+    if (mu->_track_memory_usage) {
+      mu->ns_mark_pointer(ptr, size, ref_ptr);
+    }
+  }
+}
+
 #if defined(WIN32_VC) && defined(_DEBUG)
 #if defined(WIN32_VC) && defined(_DEBUG)
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: MemoryUsage::win32_malloc_hook
 //     Function: MemoryUsage::win32_malloc_hook
@@ -352,12 +384,14 @@ MemoryUsage() {
     // this class.
     // this class.
     global_operator_new = &operator_new_handler;
     global_operator_new = &operator_new_handler;
     global_operator_delete = &operator_delete_handler;
     global_operator_delete = &operator_delete_handler;
+    global_mark_pointer = &mark_pointer_handler;
   }
   }
 
 
 #if defined(WIN32_VC) && defined(_DEBUG)
 #if defined(WIN32_VC) && defined(_DEBUG)
   if (_count_memory_usage) {
   if (_count_memory_usage) {
     global_operator_new = &operator_new_handler;
     global_operator_new = &operator_new_handler;
     global_operator_delete = &operator_delete_handler;
     global_operator_delete = &operator_delete_handler;
+    global_mark_pointer = &mark_pointer_handler;
     _CrtSetAllocHook(&win32_malloc_hook);
     _CrtSetAllocHook(&win32_malloc_hook);
   }
   }
 #endif
 #endif
@@ -399,30 +433,28 @@ ns_record_pointer(ReferenceCount *ptr) {
     // calls by toggling _recursion_protect while we adjust it.
     // calls by toggling _recursion_protect while we adjust it.
     _recursion_protect = true;
     _recursion_protect = true;
     pair<Table::iterator, bool> insert_result =
     pair<Table::iterator, bool> insert_result =
-      _table.insert(Table::value_type((void *)ptr, MemoryInfo()));
+      _table.insert(Table::value_type((void *)ptr, NULL));
     
     
     // This shouldn't fail.
     // This shouldn't fail.
     assert(insert_result.first != _table.end());
     assert(insert_result.first != _table.end());
 
 
     if (insert_result.second) {
     if (insert_result.second) {
-      _count++;
+      (*insert_result.first).second = new MemoryInfo;
+      ++_count;
     }
     }
 
 
-    MemoryInfo &info = (*insert_result.first).second;
+    MemoryInfo *info = (*insert_result.first).second;
 
 
-    // We shouldn't already have a ReferenceCount pointer.
-    if ((info._flags & MemoryInfo::F_got_ref) != 0) {
-      express_cat.error()
-        << "ReferenceCount pointer " << (void *)ptr << " recorded twice!\n";
-    }
+    // We might already have a ReferenceCount pointer, thanks to a
+    // previous call to mark_pointer().
+    nassertv(info->_ref_ptr == NULL || info->_ref_ptr == ptr);
 
 
-    info._void_ptr = (void *)ptr;
-    info._ref_ptr = ptr;
-    info._static_type = ReferenceCount::get_class_type();
-    info._dynamic_type = ReferenceCount::get_class_type();
-    info._time = TrueClock::get_global_ptr()->get_long_time();
-    info._freeze_index = _freeze_index;
-    info._flags |= (MemoryInfo::F_reconsider_dynamic_type | MemoryInfo::F_got_ref);
+    info->_ref_ptr = ptr;
+    info->_static_type = ReferenceCount::get_class_type();
+    info->_dynamic_type = ReferenceCount::get_class_type();
+    info->_time = TrueClock::get_global_ptr()->get_long_time();
+    info->_freeze_index = _freeze_index;
+    info->_flags |= MemoryInfo::F_reconsider_dynamic_type;
 
 
     // We close the recursion_protect flag all the way down here, so
     // We close the recursion_protect flag all the way down here, so
     // that we also protect ourselves against a possible recursive
     // that we also protect ourselves against a possible recursive
@@ -453,9 +485,10 @@ ns_update_type(ReferenceCount *ptr, TypeHandle type) {
       return;
       return;
     }
     }
 
 
-    MemoryInfo &info = (*ti).second;
-    info.update_type_handle(info._static_type, type);
-    info.determine_dynamic_type();
+    MemoryInfo *info = (*ti).second;
+
+    info->update_type_handle(info->_static_type, type);
+    info->determine_dynamic_type();
 
 
     consolidate_void_ptr(info);
     consolidate_void_ptr(info);
   }
   }
@@ -484,9 +517,9 @@ ns_update_type(ReferenceCount *ptr, TypedObject *typed_ptr) {
       return;
       return;
     }
     }
 
 
-    MemoryInfo &info = (*ti).second;
-    info._typed_ptr = typed_ptr;
-    info.determine_dynamic_type();
+    MemoryInfo *info = (*ti).second;
+    info->_typed_ptr = typed_ptr;
+    info->determine_dynamic_type();
 
 
     consolidate_void_ptr(info);
     consolidate_void_ptr(info);
   }
   }
@@ -512,46 +545,52 @@ ns_remove_pointer(ReferenceCount *ptr) {
       return;
       return;
     }
     }
 
 
-    MemoryInfo &info = (*ti).second;
+    MemoryInfo *info = (*ti).second;
 
 
-    if ((info._flags & MemoryInfo::F_got_ref) == 0) {
+    if (info->_ref_ptr == NULL) {
       express_cat.error()
       express_cat.error()
         << "Pointer " << (void *)ptr << " deleted twice!\n";
         << "Pointer " << (void *)ptr << " deleted twice!\n";
       return;
       return;
     }
     }
+    nassertv(info->_ref_ptr == ptr);
 
 
-    info._flags &= ~MemoryInfo::F_got_ref;
+    if (express_cat.is_spam()) {
+      express_cat.spam()
+        << "Removing ReferenceCount pointer " << (void *)ptr << "\n";
+    }
 
 
-    // Since the pointer has been destructed, we can't safely call its
-    // TypedObject virtual methods any more.  Better clear out the
-    // typed_ptr for good measure.
-    info._typed_ptr = (TypedObject *)NULL;
+    info->_ref_ptr = (ReferenceCount *)NULL;
+    info->_typed_ptr = (TypedObject *)NULL;
 
 
-    if (info._freeze_index == _freeze_index) {
+    if (info->_freeze_index == _freeze_index) {
       double now = TrueClock::get_global_ptr()->get_long_time();
       double now = TrueClock::get_global_ptr()->get_long_time();
 
 
       // We have to protect modifications to the table from recursive
       // We have to protect modifications to the table from recursive
       // calls by toggling _recursion_protect while we adjust it.
       // calls by toggling _recursion_protect while we adjust it.
       _recursion_protect = true;
       _recursion_protect = true;
-      _trend_types.add_info(info.get_type(), info);
-      _trend_ages.add_info(now - info._time, info);
+      _trend_types.add_info(info->get_type(), info);
+      _trend_ages.add_info(now - info->_time, info);
       _recursion_protect = false;
       _recursion_protect = false;
     }
     }
 
 
-    if ((info._flags & (MemoryInfo::F_got_ref | MemoryInfo::F_got_void)) == 0) {
-      // If we don't expect to call any more remove_*_pointer on this
-      // pointer, remove it from the table.
-      if (info._freeze_index == _freeze_index) {
-        _count--;
-        _current_cpp_size -= info._size;
-      }
-      _cpp_size -= info._size;
+    if (ptr != info->_void_ptr || info->_void_ptr == NULL) {
+      // Remove the entry from the table.
 
 
       // We have to protect modifications to the table from recursive
       // We have to protect modifications to the table from recursive
       // calls by toggling _recursion_protect while we adjust it.
       // calls by toggling _recursion_protect while we adjust it.
       _recursion_protect = true;
       _recursion_protect = true;
       _table.erase(ti);
       _table.erase(ti);
       _recursion_protect = false;
       _recursion_protect = false;
+
+      if (info->_void_ptr == NULL) {
+        // That was the last entry.  Remove it altogether.
+        _cpp_size -= info->_size;
+        if (info->_freeze_index == _freeze_index) {
+          _current_cpp_size -= info->_size;
+          _count--;
+        }
+        delete info;
+      }
     }
     }
   }
   }
 }
 }
@@ -576,35 +615,36 @@ ns_record_void_pointer(void *ptr, size_t size) {
 
 
     _recursion_protect = true;
     _recursion_protect = true;
     pair<Table::iterator, bool> insert_result =
     pair<Table::iterator, bool> insert_result =
-      _table.insert(Table::value_type((void *)ptr, MemoryInfo()));
+      _table.insert(Table::value_type((void *)ptr, NULL));
 
 
     assert(insert_result.first != _table.end());
     assert(insert_result.first != _table.end());
 
 
     if (insert_result.second) {
     if (insert_result.second) {
-      _count++;
+      (*insert_result.first).second = new MemoryInfo;
+      ++_count;
     }
     }
 
 
-    MemoryInfo &info = (*insert_result.first).second;
+    MemoryInfo *info = (*insert_result.first).second;
 
 
     // We shouldn't already have a void pointer.
     // We shouldn't already have a void pointer.
-    if ((info._flags & MemoryInfo::F_got_void) != 0) {
+    if (info->_void_ptr != (void *)NULL) {
       express_cat.error()
       express_cat.error()
         << "Void pointer " << (void *)ptr << " recorded twice!\n";
         << "Void pointer " << (void *)ptr << " recorded twice!\n";
       nassertv(false);
       nassertv(false);
     }
     }
 
 
-    if (info._freeze_index == _freeze_index) {
-      _current_cpp_size += size - info._size;
+    if (info->_freeze_index == _freeze_index) {
+      _current_cpp_size += size - info->_size;
     } else {
     } else {
       _current_cpp_size += size;
       _current_cpp_size += size;
     }
     }
-    _cpp_size += size - info._size;
+    _cpp_size += size - info->_size;
 
 
-    info._void_ptr = ptr;
-    info._size = size;
-    info._time = TrueClock::get_global_ptr()->get_long_time();
-    info._freeze_index = _freeze_index;
-    info._flags |= (MemoryInfo::F_got_void | MemoryInfo::F_size_known);
+    info->_void_ptr = ptr;
+    info->_size = size;
+    info->_time = TrueClock::get_global_ptr()->get_long_time();
+    info->_freeze_index = _freeze_index;
+    info->_flags |= MemoryInfo::F_size_known;
 
 
     // We close the recursion_protect flag all the way down here, so
     // We close the recursion_protect flag all the way down here, so
     // that we also protect ourselves against a possible recursive
     // that we also protect ourselves against a possible recursive
@@ -640,38 +680,94 @@ ns_remove_void_pointer(void *ptr) {
       return;
       return;
     }
     }
 
 
-    MemoryInfo &info = (*ti).second;
+    MemoryInfo *info = (*ti).second;
 
 
-    if ((info._flags & MemoryInfo::F_got_void) == 0) {
+    if (info->_void_ptr == (void *)NULL) {
       express_cat.error()
       express_cat.error()
         << "Pointer " << (void *)ptr << " deleted twice!\n";
         << "Pointer " << (void *)ptr << " deleted twice!\n";
       return;
       return;
     }
     }
+    nassertv(info->_void_ptr == ptr);
 
 
-    if ((info._flags & MemoryInfo::F_got_ref) != 0) {
+    if (info->_ref_ptr != (ReferenceCount *)NULL) {
       express_cat.error()
       express_cat.error()
         << "Pointer " << (void *)ptr
         << "Pointer " << (void *)ptr
         << " did not destruct before being deleted!\n";
         << " did not destruct before being deleted!\n";
+      if (info->_ref_ptr != ptr) {
+        remove_pointer(info->_ref_ptr);
+      }
     }
     }
 
 
-    info._flags &= ~MemoryInfo::F_got_void;
+    info->_void_ptr = NULL;
 
 
-    if ((info._flags & (MemoryInfo::F_got_ref | MemoryInfo::F_got_void)) == 0) {
-      // If we don't expect to call any more remove_*_pointer on this
-      // pointer, remove it from the table.
+    // Remove it from the table.
 
 
-      if (info._freeze_index == _freeze_index) {
-        _count--;
-        _current_cpp_size -= info._size;
-      }
-      _cpp_size -= info._size;
+    // We have to protect modifications to the table from recursive
+    // calls by toggling _recursion_protect while we adjust it.
+    _recursion_protect = true;
+    _table.erase(ti);
+    _recursion_protect = false;
 
 
-      // We have to protect modifications to the table from recursive
-      // calls by toggling _recursion_protect while we adjust it.
-      _recursion_protect = true;
-      _table.erase(ti);
-      _recursion_protect = false;
+    _cpp_size -= info->_size;
+    if (info->_freeze_index == _freeze_index) {
+      --_count;
+      _current_cpp_size -= info->_size;
+    }
+    delete info;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MemoryUsage::ns_mark_pointer
+//       Access: Private
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void MemoryUsage::
+ns_mark_pointer(void *ptr, size_t size, ReferenceCount *ref_ptr) {
+  if (express_cat.is_spam()) {
+    express_cat.spam()
+      << "Marking pointer " << ptr << ", size " << size 
+      << ", ref_ptr = " << ref_ptr << "\n";
+  }
+
+  if (size != 0) {
+    // We're recording this pointer as now in use.
+    ns_record_void_pointer(ptr, size);
+
+    if (ref_ptr != (ReferenceCount *)NULL) {
+      // Make the pointer typed.  This is particularly necessary in
+      // case the ref_ptr is a different value than the base void
+      // pointer; this may be our only opportunity to associate the
+      // two pointers.
+      Table::iterator ti;
+      ti = _table.find(ptr);
+      nassertv(ti != _table.end());
+      MemoryInfo *info = (*ti).second;
+
+      info->_ref_ptr = ref_ptr;
+      info->_static_type = ReferenceCount::get_class_type();
+      info->_dynamic_type = ReferenceCount::get_class_type();
+      info->_flags |= MemoryInfo::F_reconsider_dynamic_type;
+      
+      if (ref_ptr != ptr) {
+        _recursion_protect = true;
+        
+        pair<Table::iterator, bool> insert_result =
+          _table.insert(Table::value_type((void *)ref_ptr, info));
+        assert(insert_result.first != _table.end());
+        if (!insert_result.second) {
+          express_cat.warning()
+            << "Attempt to mark pointer " << ptr << " as ReferenceCount "
+            << ref_ptr << ", which was already allocated.\n";
+        }
+        
+        _recursion_protect = false;
+      }
     }
     }
+
+  } else {
+    // We're removing this pointer from use.
+    ns_remove_void_pointer(ptr);
   }
   }
 }
 }
 
 
@@ -757,11 +853,11 @@ ns_get_pointers(MemoryUsagePointers &result) {
   double now = TrueClock::get_global_ptr()->get_long_time();
   double now = TrueClock::get_global_ptr()->get_long_time();
   Table::iterator ti;
   Table::iterator ti;
   for (ti = _table.begin(); ti != _table.end(); ++ti) {
   for (ti = _table.begin(); ti != _table.end(); ++ti) {
-    MemoryInfo &info = (*ti).second;
-    if (info._freeze_index == _freeze_index &&
-        info._ref_ptr != (ReferenceCount *)NULL) {
-      result.add_entry(info._ref_ptr, info._typed_ptr, info.get_type(),
-                       now - info._time);
+    MemoryInfo *info = (*ti).second;
+    if (info->_freeze_index == _freeze_index &&
+        info->_ref_ptr != (ReferenceCount *)NULL) {
+      result.add_entry(info->_ref_ptr, info->_typed_ptr, info->get_type(),
+                       now - info->_time);
     }
     }
   }
   }
 }
 }
@@ -781,14 +877,14 @@ ns_get_pointers_of_type(MemoryUsagePointers &result, TypeHandle type) {
   double now = TrueClock::get_global_ptr()->get_long_time();
   double now = TrueClock::get_global_ptr()->get_long_time();
   Table::iterator ti;
   Table::iterator ti;
   for (ti = _table.begin(); ti != _table.end(); ++ti) {
   for (ti = _table.begin(); ti != _table.end(); ++ti) {
-    MemoryInfo &info = (*ti).second;
-    if (info._freeze_index == _freeze_index &&
-        info._ref_ptr != (ReferenceCount *)NULL) {
-      TypeHandle info_type = info.get_type();
+    MemoryInfo *info = (*ti).second;
+    if (info->_freeze_index == _freeze_index &&
+        info->_ref_ptr != (ReferenceCount *)NULL) {
+      TypeHandle info_type = info->get_type();
       if (info_type != TypeHandle::none() &&
       if (info_type != TypeHandle::none() &&
           info_type.is_derived_from(type)) {
           info_type.is_derived_from(type)) {
-        result.add_entry(info._ref_ptr, info._typed_ptr, info_type,
-                         now - info._time);
+        result.add_entry(info->_ref_ptr, info->_typed_ptr, info_type,
+                         now - info->_time);
       }
       }
     }
     }
   }
   }
@@ -810,13 +906,13 @@ ns_get_pointers_of_age(MemoryUsagePointers &result,
   double now = TrueClock::get_global_ptr()->get_long_time();
   double now = TrueClock::get_global_ptr()->get_long_time();
   Table::iterator ti;
   Table::iterator ti;
   for (ti = _table.begin(); ti != _table.end(); ++ti) {
   for (ti = _table.begin(); ti != _table.end(); ++ti) {
-    MemoryInfo &info = (*ti).second;
-    if (info._freeze_index == _freeze_index &&
-        info._ref_ptr != (ReferenceCount *)NULL) {
-      double age = now - info._time;
+    MemoryInfo *info = (*ti).second;
+    if (info->_freeze_index == _freeze_index &&
+        info->_ref_ptr != (ReferenceCount *)NULL) {
+      double age = now - info->_time;
       if ((age >= from && age <= to) ||
       if ((age >= from && age <= to) ||
           (age >= to && age <= from)) {
           (age >= to && age <= from)) {
-        result.add_entry(info._ref_ptr, info._typed_ptr, info.get_type(), age);
+        result.add_entry(info->_ref_ptr, info->_typed_ptr, info->get_type(), age);
       }
       }
     }
     }
   }
   }
@@ -853,13 +949,13 @@ ns_get_pointers_with_zero_count(MemoryUsagePointers &result) {
   double now = TrueClock::get_global_ptr()->get_long_time();
   double now = TrueClock::get_global_ptr()->get_long_time();
   Table::iterator ti;
   Table::iterator ti;
   for (ti = _table.begin(); ti != _table.end(); ++ti) {
   for (ti = _table.begin(); ti != _table.end(); ++ti) {
-    MemoryInfo &info = (*ti).second;
-    if (info._freeze_index == _freeze_index && 
-        info._ref_ptr != (ReferenceCount *)NULL) {
-      if (info._ref_ptr->get_ref_count() == 0) {
-        info._ref_ptr->ref();
-        result.add_entry(info._ref_ptr, info._typed_ptr, info.get_type(),
-                         now - info._time);
+    MemoryInfo *info = (*ti).second;
+    if (info->_freeze_index == _freeze_index && 
+        info->_ref_ptr != (ReferenceCount *)NULL) {
+      if (info->_ref_ptr->get_ref_count() == 0) {
+        info->_ref_ptr->ref();
+        result.add_entry(info->_ref_ptr, info->_typed_ptr, info->get_type(),
+                         now - info->_time);
       }
       }
     }
     }
   }
   }
@@ -901,9 +997,9 @@ ns_show_current_types() {
   
   
   Table::iterator ti;
   Table::iterator ti;
   for (ti = _table.begin(); ti != _table.end(); ++ti) {
   for (ti = _table.begin(); ti != _table.end(); ++ti) {
-    MemoryInfo &info = (*ti).second;
-    if (info._freeze_index == _freeze_index) {
-      hist.add_info(info.get_type(), info);
+    MemoryInfo *info = (*ti).second;
+    if (info->_freeze_index == _freeze_index) {
+      hist.add_info(info->get_type(), info);
     }
     }
   }
   }
 
 
@@ -942,9 +1038,9 @@ ns_show_current_ages() {
 
 
   Table::iterator ti;
   Table::iterator ti;
   for (ti = _table.begin(); ti != _table.end(); ++ti) {
   for (ti = _table.begin(); ti != _table.end(); ++ti) {
-    MemoryInfo &info = (*ti).second;
-    if (info._freeze_index == _freeze_index) {
-      hist.add_info(now - info._time, info);
+    MemoryInfo *info = (*ti).second;
+    if (info->_freeze_index == _freeze_index) {
+      hist.add_info(now - info->_time, info);
     }
     }
   }
   }
 
 
@@ -975,26 +1071,28 @@ ns_show_trend_ages() {
 //               before ReferenceCount, e.g. TypedReferenceCount).
 //               before ReferenceCount, e.g. TypedReferenceCount).
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void MemoryUsage::
 void MemoryUsage::
-consolidate_void_ptr(MemoryInfo &info) {
-  if (info.is_size_known()) {
+consolidate_void_ptr(MemoryInfo *info) {
+  if (info->is_size_known()) {
     // We already know the size, so no sweat.
     // We already know the size, so no sweat.
     return;
     return;
   }
   }
 
 
-  if (info.get_typed_ptr() == (TypedObject *)NULL) {
+  if (info->_typed_ptr == (TypedObject *)NULL) {
     // We don't have a typed pointer for this thing yet.
     // We don't have a typed pointer for this thing yet.
     return;
     return;
   }
   }
+  
+  TypedObject *typed_ptr = info->_typed_ptr;
 
 
-  void *typed_ptr = (void *)info.get_typed_ptr();
-
-  if (typed_ptr == (void *)info.get_ref_ptr()) {
+  if ((void *)typed_ptr == (void *)info->_ref_ptr) {
     // The TypedObject pointer is the same pointer as the
     // The TypedObject pointer is the same pointer as the
     // ReferenceCount pointer, so there's no point in looking it up
     // ReferenceCount pointer, so there's no point in looking it up
     // separately.  Actually, this really shouldn't even be possible.
     // separately.  Actually, this really shouldn't even be possible.
     return;
     return;
   }
   }
 
 
+  nassertv(info->_void_ptr == NULL);
+
   Table::iterator ti;
   Table::iterator ti;
   ti = _table.find(typed_ptr);
   ti = _table.find(typed_ptr);
   if (ti == _table.end()) {
   if (ti == _table.end()) {
@@ -1003,32 +1101,29 @@ consolidate_void_ptr(MemoryInfo &info) {
   }
   }
 
 
   // We do have an entry!  Copy over the relevant pieces.
   // We do have an entry!  Copy over the relevant pieces.
-  MemoryInfo &typed_info = (*ti).second;
+  MemoryInfo *typed_info = (*ti).second;
 
 
-  if (typed_info.is_size_known()) {
-    info._size = typed_info.get_size();
-    info._flags |= MemoryInfo::F_size_known;
-    if (typed_info._freeze_index == _freeze_index) {
-      _current_cpp_size += info._size;
+  nassertv(typed_info->_void_ptr == typed_ptr &&
+           typed_info->_ref_ptr == NULL);
+
+  info->_void_ptr = typed_info->_void_ptr;
+  if (typed_info->is_size_known()) {
+    info->_size = typed_info->get_size();
+    info->_flags |= MemoryInfo::F_size_known;
+    if (typed_info->_freeze_index == _freeze_index) {
+      _current_cpp_size += info->_size;
     }
     }
   }
   }
 
 
-  // The typed_ptr is clearly the more accurate pointer to the
-  // beginning of the structure.
-  info._void_ptr = typed_ptr;
-
-  // Now that we've consolidated the pointers, remove the void pointer
-  // entry.
-  if (info._freeze_index == _freeze_index) {
+  // Now that we've consolidated the pointers, remove the entry for
+  // the typed pointer.
+  if (info->_freeze_index == _freeze_index) {
     _count--;
     _count--;
-    _current_cpp_size -= info._size;
+    _current_cpp_size -= info->_size;
   }
   }
-    
-  // We have to protect modifications to the table from recursive
-  // calls by toggling _recursion_protect while we adjust it.
-  _recursion_protect = true;
-  _table.erase(ti);
-  _recursion_protect = false;
+
+  delete typed_info;
+  (*ti).second = info;
 }
 }
 
 
 
 

+ 17 - 4
panda/src/express/memoryUsage.h

@@ -54,6 +54,8 @@ public:
 public:
 public:
   static void *operator_new_handler(size_t size);
   static void *operator_new_handler(size_t size);
   static void operator_delete_handler(void *ptr);
   static void operator_delete_handler(void *ptr);
+  static void mark_pointer_handler(void *ptr, size_t size, 
+                                   ReferenceCount *ref_ptr);
 
 
 #if defined(WIN32_VC) && defined(_DEBUG)
 #if defined(WIN32_VC) && defined(_DEBUG)
   static int win32_malloc_hook(int alloc_type, void *ptr, 
   static int win32_malloc_hook(int alloc_type, void *ptr, 
@@ -98,6 +100,7 @@ private:
 
 
   void ns_record_void_pointer(void *ptr, size_t size);
   void ns_record_void_pointer(void *ptr, size_t size);
   void ns_remove_void_pointer(void *ptr);
   void ns_remove_void_pointer(void *ptr);
+  void ns_mark_pointer(void *ptr, size_t size, ReferenceCount *ref_ptr);
 
 
   size_t ns_get_current_cpp_size();
   size_t ns_get_current_cpp_size();
   size_t ns_get_cpp_size();
   size_t ns_get_cpp_size();
@@ -117,7 +120,7 @@ private:
   void ns_show_current_ages();
   void ns_show_current_ages();
   void ns_show_trend_ages();
   void ns_show_trend_ages();
 
 
-  void consolidate_void_ptr(MemoryInfo &info);
+  void consolidate_void_ptr(MemoryInfo *info);
 
 
   static MemoryUsage *_global_ptr;
   static MemoryUsage *_global_ptr;
 
 
@@ -125,8 +128,18 @@ private:
   // Actually, it turns out that it doesn't matter, since somehow the
   // Actually, it turns out that it doesn't matter, since somehow the
   // pallocator gets used even though we specify dallocator here, so
   // pallocator gets used even though we specify dallocator here, so
   // we have to make special code that handles the recursion anyway.
   // we have to make special code that handles the recursion anyway.
-  typedef map<void *, MemoryInfo, less<void *>, dallocator<pair<void * const, MemoryInfo> > > Table;
+
+  // This table stores up to two entiries for each MemoryInfo object:
+  // one for the void pointer (the pointer to the beginning of the
+  // allocated memory block), and one for the ReferenceCount pointer.
+  // For a particular object, these two pointers may be the same or
+  // they may be different.  Some objects may be stored under both
+  // pointers, while others may be stored under only one pointer or
+  // the other.  We don't store an entry for an object's TypedObject
+  // pointer.
+  typedef map<void *, MemoryInfo *, less<void *>, dallocator<pair<void * const, MemoryInfo *> > > Table;
   Table _table;
   Table _table;
+
   int _freeze_index;
   int _freeze_index;
   int _count;
   int _count;
   size_t _current_cpp_size;
   size_t _current_cpp_size;
@@ -136,7 +149,7 @@ private:
 
 
   class TypeHistogram {
   class TypeHistogram {
   public:
   public:
-    void add_info(TypeHandle type, MemoryInfo &info);
+    void add_info(TypeHandle type, MemoryInfo *info);
     void show() const;
     void show() const;
     void clear();
     void clear();
 
 
@@ -151,7 +164,7 @@ private:
   class AgeHistogram {
   class AgeHistogram {
   public:
   public:
     AgeHistogram();
     AgeHistogram();
-    void add_info(double age, MemoryInfo &info);
+    void add_info(double age, MemoryInfo *info);
     void show() const;
     void show() const;
     void clear();
     void clear();
 
 

+ 3 - 3
panda/src/express/memoryUsagePointerCounts.cxx

@@ -28,11 +28,11 @@
 //  Description: Adds a pointer definition to the counter.
 //  Description: Adds a pointer definition to the counter.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void MemoryUsagePointerCounts::
 void MemoryUsagePointerCounts::
-add_info(MemoryInfo &info) {
+add_info(MemoryInfo *info) {
   _count++;
   _count++;
 
 
-  if (info.is_size_known()) {
-    _size += info.get_size();
+  if (info->is_size_known()) {
+    _size += info->get_size();
   } else {
   } else {
     _unknown_size_count++;
     _unknown_size_count++;
   }
   }

+ 1 - 1
panda/src/express/memoryUsagePointerCounts.h

@@ -40,7 +40,7 @@ public:
   INLINE void operator = (const MemoryUsagePointerCounts &copy);
   INLINE void operator = (const MemoryUsagePointerCounts &copy);
 
 
   INLINE void clear();
   INLINE void clear();
-  void add_info(MemoryInfo &info);
+  void add_info(MemoryInfo *info);
   void output(ostream &out) const;
   void output(ostream &out) const;
 
 
   INLINE bool is_size_unknown() const;
   INLINE bool is_size_unknown() const;

+ 51 - 30
panda/src/gobj/geomVertexArrayData.cxx

@@ -63,6 +63,7 @@ ALLOC_DELETED_CHAIN_DEF(GeomVertexArrayDataHandle);
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 GeomVertexArrayData::
 GeomVertexArrayData::
 GeomVertexArrayData() : SimpleLruPage(0) {
 GeomVertexArrayData() : SimpleLruPage(0) {
+  _contexts = NULL;
   _endian_reversed = false;
   _endian_reversed = false;
 
 
   // Can't put it in the LRU until it has been read in and made valid.
   // Can't put it in the LRU until it has been read in and made valid.
@@ -95,6 +96,7 @@ GeomVertexArrayData(const GeomVertexArrayFormat *array_format,
   }
   }
   CLOSE_ITERATE_ALL_STAGES(_cycler);
   CLOSE_ITERATE_ALL_STAGES(_cycler);
 
 
+  _contexts = NULL;
   _endian_reversed = false;
   _endian_reversed = false;
 
 
   set_lru_size(0);
   set_lru_size(0);
@@ -113,6 +115,7 @@ GeomVertexArrayData(const GeomVertexArrayData &copy) :
   _array_format(copy._array_format),
   _array_format(copy._array_format),
   _cycler(copy._cycler)
   _cycler(copy._cycler)
 {
 {
+  _contexts = NULL;
   _endian_reversed = false;
   _endian_reversed = false;
 
 
   copy.mark_used_lru();
   copy.mark_used_lru();
@@ -221,9 +224,12 @@ prepare(PreparedGraphicsObjects *prepared_objects) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool GeomVertexArrayData::
 bool GeomVertexArrayData::
 is_prepared(PreparedGraphicsObjects *prepared_objects) const {
 is_prepared(PreparedGraphicsObjects *prepared_objects) const {
+  if (_contexts == (Contexts *)NULL) {
+    return false;
+  }
   Contexts::const_iterator ci;
   Contexts::const_iterator ci;
-  ci = _contexts.find(prepared_objects);
-  if (ci != _contexts.end()) {
+  ci = _contexts->find(prepared_objects);
+  if (ci != _contexts->end()) {
     return true;
     return true;
   }
   }
   return prepared_objects->is_vertex_buffer_queued(this);
   return prepared_objects->is_vertex_buffer_queued(this);
@@ -248,15 +254,18 @@ is_prepared(PreparedGraphicsObjects *prepared_objects) const {
 VertexBufferContext *GeomVertexArrayData::
 VertexBufferContext *GeomVertexArrayData::
 prepare_now(PreparedGraphicsObjects *prepared_objects, 
 prepare_now(PreparedGraphicsObjects *prepared_objects, 
             GraphicsStateGuardianBase *gsg) {
             GraphicsStateGuardianBase *gsg) {
+  if (_contexts == (Contexts *)NULL) {
+    _contexts = new Contexts;
+  }
   Contexts::const_iterator ci;
   Contexts::const_iterator ci;
-  ci = _contexts.find(prepared_objects);
-  if (ci != _contexts.end()) {
+  ci = _contexts->find(prepared_objects);
+  if (ci != _contexts->end()) {
     return (*ci).second;
     return (*ci).second;
   }
   }
 
 
   VertexBufferContext *vbc = prepared_objects->prepare_vertex_buffer_now(this, gsg);
   VertexBufferContext *vbc = prepared_objects->prepare_vertex_buffer_now(this, gsg);
   if (vbc != (VertexBufferContext *)NULL) {
   if (vbc != (VertexBufferContext *)NULL) {
-    _contexts[prepared_objects] = vbc;
+    (*_contexts)[prepared_objects] = vbc;
   }
   }
   return vbc;
   return vbc;
 }
 }
@@ -270,12 +279,14 @@ prepare_now(PreparedGraphicsObjects *prepared_objects,
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool GeomVertexArrayData::
 bool GeomVertexArrayData::
 release(PreparedGraphicsObjects *prepared_objects) {
 release(PreparedGraphicsObjects *prepared_objects) {
-  Contexts::iterator ci;
-  ci = _contexts.find(prepared_objects);
-  if (ci != _contexts.end()) {
-    VertexBufferContext *vbc = (*ci).second;
-    prepared_objects->release_vertex_buffer(vbc);
-    return true;
+  if (_contexts != (Contexts *)NULL) {
+    Contexts::iterator ci;
+    ci = _contexts->find(prepared_objects);
+    if (ci != _contexts->end()) {
+      VertexBufferContext *vbc = (*ci).second;
+      prepared_objects->release_vertex_buffer(vbc);
+      return true;
+    }
   }
   }
 
 
   // Maybe it wasn't prepared yet, but it's about to be.
   // Maybe it wasn't prepared yet, but it's about to be.
@@ -291,24 +302,28 @@ release(PreparedGraphicsObjects *prepared_objects) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 int GeomVertexArrayData::
 int GeomVertexArrayData::
 release_all() {
 release_all() {
-  // We have to traverse a copy of the _contexts list, because the
-  // PreparedGraphicsObjects object will call clear_prepared() in response
-  // to each release_vertex_buffer(), and we don't want to be modifying the
-  // _contexts list while we're traversing it.
-  Contexts temp = _contexts;
-  int num_freed = (int)_contexts.size();
-
-  Contexts::const_iterator ci;
-  for (ci = temp.begin(); ci != temp.end(); ++ci) {
-    PreparedGraphicsObjects *prepared_objects = (*ci).first;
-    VertexBufferContext *vbc = (*ci).second;
-    prepared_objects->release_vertex_buffer(vbc);
+  int num_freed = 0;
+
+  if (_contexts != (Contexts *)NULL) {
+    // We have to traverse a copy of the _contexts list, because the
+    // PreparedGraphicsObjects object will call clear_prepared() in response
+    // to each release_vertex_buffer(), and we don't want to be modifying the
+    // _contexts list while we're traversing it.
+    Contexts temp = *_contexts;
+    num_freed = (int)_contexts->size();
+    
+    Contexts::const_iterator ci;
+    for (ci = temp.begin(); ci != temp.end(); ++ci) {
+      PreparedGraphicsObjects *prepared_objects = (*ci).first;
+      VertexBufferContext *vbc = (*ci).second;
+      prepared_objects->release_vertex_buffer(vbc);
+    }
+    
+    // Now that we've called release_vertex_buffer() on every known context,
+    // the _contexts list should have completely emptied itself.
+    nassertr(_contexts == NULL, num_freed);
   }
   }
 
 
-  // Now that we've called release_vertex_buffer() on every known context,
-  // the _contexts list should have completely emptied itself.
-  nassertr(_contexts.empty(), num_freed);
-
   return num_freed;
   return num_freed;
 }
 }
 
 
@@ -360,10 +375,16 @@ evict_lru() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void GeomVertexArrayData::
 void GeomVertexArrayData::
 clear_prepared(PreparedGraphicsObjects *prepared_objects) {
 clear_prepared(PreparedGraphicsObjects *prepared_objects) {
+  nassertv(_contexts != (Contexts *)NULL);
+
   Contexts::iterator ci;
   Contexts::iterator ci;
-  ci = _contexts.find(prepared_objects);
-  if (ci != _contexts.end()) {
-    _contexts.erase(ci);
+  ci = _contexts->find(prepared_objects);
+  if (ci != _contexts->end()) {
+    _contexts->erase(ci);
+    if (_contexts->empty()) {
+      delete _contexts;
+      _contexts = NULL;
+    }
   } else {
   } else {
     // If this assertion fails, clear_prepared() was given a
     // If this assertion fails, clear_prepared() was given a
     // prepared_objects which the data array didn't know about.
     // prepared_objects which the data array didn't know about.

+ 1 - 1
panda/src/gobj/geomVertexArrayData.h

@@ -133,7 +133,7 @@ private:
   // have been prepared there.  When either destructs, it removes
   // have been prepared there.  When either destructs, it removes
   // itself from the other's list.
   // itself from the other's list.
   typedef pmap<PreparedGraphicsObjects *, VertexBufferContext *> Contexts;
   typedef pmap<PreparedGraphicsObjects *, VertexBufferContext *> Contexts;
-  Contexts _contexts;
+  Contexts *_contexts;
 
 
   // This is only used when reading from a bam file.  It is set true
   // This is only used when reading from a bam file.  It is set true
   // to indicate the data must be endian-reversed in finalize().
   // to indicate the data must be endian-reversed in finalize().

+ 44 - 8
panda/src/pstatclient/pStatClient.cxx

@@ -31,10 +31,13 @@
 #include "pStatProperties.h"
 #include "pStatProperties.h"
 #include "thread.h"
 #include "thread.h"
 #include "clockObject.h"
 #include "clockObject.h"
+#include "neverFreeMemory.h"
 
 
 PStatCollector PStatClient::_total_size_pcollector("Main memory");
 PStatCollector PStatClient::_total_size_pcollector("Main memory");
 PStatCollector PStatClient::_cpp_size_pcollector("Main memory:C++");
 PStatCollector PStatClient::_cpp_size_pcollector("Main memory:C++");
 PStatCollector PStatClient::_cpp_other_size_pcollector("Main memory:C++:Other");
 PStatCollector PStatClient::_cpp_other_size_pcollector("Main memory:C++:Other");
+PStatCollector PStatClient::_nf_unused_size_pcollector("Main memory:NeverFree:Unused");
+PStatCollector PStatClient::_nf_other_size_pcollector("Main memory:NeverFree:Other");
 PStatCollector PStatClient::_interpreter_size_pcollector("Main memory:Interpreter");
 PStatCollector PStatClient::_interpreter_size_pcollector("Main memory:Interpreter");
 PStatCollector PStatClient::_pstats_pcollector("*:PStats");
 PStatCollector PStatClient::_pstats_pcollector("*:PStats");
 PStatCollector PStatClient::_clock_wait_pcollector("Wait:Clock Wait:Sleep");
 PStatCollector PStatClient::_clock_wait_pcollector("Wait:Clock Wait:Sleep");
@@ -216,7 +219,7 @@ main_tick() {
       _total_size_pcollector.set_level(MemoryUsage::get_total_size());
       _total_size_pcollector.set_level(MemoryUsage::get_total_size());
     }
     }
     if (MemoryUsage::has_cpp_size()) {
     if (MemoryUsage::has_cpp_size()) {
-      _cpp_size_pcollector.set_level(MemoryUsage::get_cpp_size());
+      _cpp_size_pcollector.set_level(MemoryUsage::get_cpp_size() - NeverFreeMemory::get_total_alloc());
     }
     }
     if (MemoryUsage::has_interpreter_size()) {
     if (MemoryUsage::has_interpreter_size()) {
       _interpreter_size_pcollector.set_level(MemoryUsage::get_interpreter_size());
       _interpreter_size_pcollector.set_level(MemoryUsage::get_interpreter_size());
@@ -229,21 +232,32 @@ main_tick() {
       type_handle_cols.push_back(TypeHandleCollector());
       type_handle_cols.push_back(TypeHandleCollector());
     }
     }
 
 
-    size_t total_usage = 0;
+    size_t cpp_total_usage = 0;
+    size_t nf_total_usage = 0;
     int i;
     int i;
     for (i = 0; i < num_typehandles; ++i) {
     for (i = 0; i < num_typehandles; ++i) {
       TypeHandle type = type_reg->get_typehandle(i);
       TypeHandle type = type_reg->get_typehandle(i);
       for (int mi = 0; mi < (int)TypeHandle::MC_limit; ++mi) {
       for (int mi = 0; mi < (int)TypeHandle::MC_limit; ++mi) {
         TypeHandle::MemoryClass mc = (TypeHandle::MemoryClass)mi;
         TypeHandle::MemoryClass mc = (TypeHandle::MemoryClass)mi;
         size_t usage = type.get_memory_usage(mc);
         size_t usage = type.get_memory_usage(mc);
-        total_usage += usage;
+
+        switch (mc) {
+        case TypeHandle::MC_deleted_chain_active:
+        case TypeHandle::MC_deleted_chain_inactive:
+          nf_total_usage += usage;
+          break;
+
+        default:
+          cpp_total_usage += usage;
+        }          
       }
       }
     }
     }
-    size_t min_usage = total_usage / 1024;
+    size_t min_usage = (cpp_total_usage + nf_total_usage) / 1024;
     if (!pstats_mem_other) {
     if (!pstats_mem_other) {
       min_usage = 0;
       min_usage = 0;
     }
     }
-    size_t other_usage = total_usage;
+    size_t cpp_other_usage = cpp_total_usage;
+    size_t nf_other_usage = nf_total_usage;
 
 
     for (i = 0; i < num_typehandles; ++i) {
     for (i = 0; i < num_typehandles; ++i) {
       TypeHandle type = type_reg->get_typehandle(i);
       TypeHandle type = type_reg->get_typehandle(i);
@@ -255,19 +269,41 @@ main_tick() {
           // We have some memory usage on this TypeHandle.  See if we
           // We have some memory usage on this TypeHandle.  See if we
           // have a collector for it.
           // have a collector for it.
           if (!col.is_valid()) {
           if (!col.is_valid()) {
+            const char *category;
+            switch (mc) {
+            case TypeHandle::MC_deleted_chain_active:
+            case TypeHandle::MC_deleted_chain_inactive:
+              category = "NeverFree";
+              break;
+
+            default:
+              category = "C++";
+            }
             ostringstream strm;
             ostringstream strm;
-            strm << "Main memory:C++:" << type << ":" << mc;
+            strm << "Main memory:" << category << ":" << type << ":" << mc;
             col = PStatCollector(strm.str());
             col = PStatCollector(strm.str());
           }
           }
           col.set_level(usage);
           col.set_level(usage);
-          other_usage -= usage;
+
+          switch (mc) {
+          case TypeHandle::MC_deleted_chain_active:
+          case TypeHandle::MC_deleted_chain_inactive:
+            nf_other_usage -= usage;
+            break;
+
+          default:
+            cpp_other_usage -= usage;
+          }
         }
         }
       }
       }
     }
     }
 
 
+    _nf_unused_size_pcollector.set_level(NeverFreeMemory::get_total_unused());
+
     // The remaining amount--all collectors smaller than 0.1% of the
     // The remaining amount--all collectors smaller than 0.1% of the
     // total--go into "other".
     // total--go into "other".
-    _cpp_other_size_pcollector.set_level(other_usage);
+    _nf_other_size_pcollector.set_level(nf_other_usage);
+    _cpp_other_size_pcollector.set_level(cpp_other_usage);
   }
   }
 #endif  // DO_MEMORY_USAGE
 #endif  // DO_MEMORY_USAGE
 
 

+ 2 - 0
panda/src/pstatclient/pStatClient.h

@@ -223,6 +223,8 @@ private:
   static PStatCollector _total_size_pcollector;
   static PStatCollector _total_size_pcollector;
   static PStatCollector _cpp_size_pcollector;
   static PStatCollector _cpp_size_pcollector;
   static PStatCollector _cpp_other_size_pcollector;
   static PStatCollector _cpp_other_size_pcollector;
+  static PStatCollector _nf_unused_size_pcollector;
+  static PStatCollector _nf_other_size_pcollector;
   static PStatCollector _interpreter_size_pcollector;
   static PStatCollector _interpreter_size_pcollector;
   static PStatCollector _pstats_pcollector;
   static PStatCollector _pstats_pcollector;
   static PStatCollector _clock_wait_pcollector;
   static PStatCollector _clock_wait_pcollector;

+ 1 - 0
panda/src/pstatclient/pStatProperties.cxx

@@ -198,6 +198,7 @@ static LevelCollectorProperties level_properties[] = {
   { 1, "Occlusion results",                { 0.3, 0.9, 0.8 },  "", 500.0 },
   { 1, "Occlusion results",                { 0.3, 0.9, 0.8 },  "", 500.0 },
   { 1, "Main memory",                      { 0.5, 1.0, 0.5 },  "MB", 64, 1048576 },
   { 1, "Main memory",                      { 0.5, 1.0, 0.5 },  "MB", 64, 1048576 },
   { 1, "Main memory:C++",                  { 0.2, 0.2, 1.0 } },
   { 1, "Main memory:C++",                  { 0.2, 0.2, 1.0 } },
+  { 1, "Main memory:NeverFree",            { 0.2, 0.5, 0.8 } },
   { 1, "Main memory:Interpreter",          { 0.8, 0.2, 0.5 } },
   { 1, "Main memory:Interpreter",          { 0.8, 0.2, 0.5 } },
   { 1, "Vertex Data",                      { 1.0, 0.4, 0.0 },  "MB", 64, 1048576 },
   { 1, "Vertex Data",                      { 1.0, 0.4, 0.0 },  "MB", 64, 1048576 },
   { 1, "Vertex Data:Small",                { 0.2, 0.3, 0.4 } },
   { 1, "Vertex Data:Small",                { 0.2, 0.3, 0.4 } },

+ 13 - 4
panda/src/putil/bamWriter.cxx

@@ -24,6 +24,7 @@
 #include "bam.h"
 #include "bam.h"
 #include "bamWriter.h"
 #include "bamWriter.h"
 #include "bamReader.h"
 #include "bamReader.h"
+#include "mutexHolder.h"
 
 
 #include <algorithm>
 #include <algorithm>
 
 
@@ -51,10 +52,12 @@ BamWriter::
   StateMap::iterator si;
   StateMap::iterator si;
   for (si = _state_map.begin(); si != _state_map.end(); ++si) {
   for (si = _state_map.begin(); si != _state_map.end(); ++si) {
     TypedWritable *object = (TypedWritable *)(*si).first;
     TypedWritable *object = (TypedWritable *)(*si).first;
+    MutexHolder holder(TypedWritable::_bam_writers_lock);
+    nassertv(object->_bam_writers != (TypedWritable::BamWriters *)NULL);
     TypedWritable::BamWriters::iterator wi = 
     TypedWritable::BamWriters::iterator wi = 
-      find(object->_bam_writers.begin(), object->_bam_writers.end(), this);
-    nassertv(wi != object->_bam_writers.end());
-    object->_bam_writers.erase(wi);
+      find(object->_bam_writers->begin(), object->_bam_writers->end(), this);
+    nassertv(wi != object->_bam_writers->end());
+    object->_bam_writers->erase(wi);
   }
   }
 }
 }
 
 
@@ -513,7 +516,13 @@ enqueue_object(const TypedWritable *object) {
     bool inserted =
     bool inserted =
       _state_map.insert(StateMap::value_type(object, StoreState(_next_object_id))).second;
       _state_map.insert(StateMap::value_type(object, StoreState(_next_object_id))).second;
     nassertr(inserted, false);
     nassertr(inserted, false);
-    ((TypedWritable *)object)->_bam_writers.push_back(this);
+    {
+      MutexHolder holder(TypedWritable::_bam_writers_lock);
+      if (object->_bam_writers == ((TypedWritable::BamWriters *)NULL)) {
+        ((TypedWritable *)object)->_bam_writers = new TypedWritable::BamWriters;
+      }
+      object->_bam_writers->push_back(this);
+    }
     _next_object_id++;
     _next_object_id++;
 
 
   } else {
   } else {

+ 2 - 2
panda/src/putil/typedWritable.I

@@ -22,7 +22,7 @@
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE TypedWritable::
 INLINE TypedWritable::
-TypedWritable() {
+TypedWritable() : _bam_writers(NULL) {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -31,7 +31,7 @@ TypedWritable() {
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE TypedWritable::
 INLINE TypedWritable::
-TypedWritable(const TypedWritable &) {
+TypedWritable(const TypedWritable &) : _bam_writers(NULL) {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 16 - 4
panda/src/putil/typedWritable.cxx

@@ -18,6 +18,9 @@
 
 
 #include "typedWritable.h"
 #include "typedWritable.h"
 #include "bamWriter.h"
 #include "bamWriter.h"
+#include "mutexHolder.h"
+
+Mutex TypedWritable::_bam_writers_lock;
 
 
 TypeHandle TypedWritable::_type_handle;
 TypeHandle TypedWritable::_type_handle;
 TypedWritable* const TypedWritable::Null = (TypedWritable*)0L;
 TypedWritable* const TypedWritable::Null = (TypedWritable*)0L;
@@ -30,10 +33,19 @@ TypedWritable* const TypedWritable::Null = (TypedWritable*)0L;
 TypedWritable::
 TypedWritable::
 ~TypedWritable() {
 ~TypedWritable() {
   // Remove the object pointer from the BamWriters that reference it.
   // Remove the object pointer from the BamWriters that reference it.
-  BamWriters::iterator wi;
-  for (wi = _bam_writers.begin(); wi != _bam_writers.end(); ++wi) {
-    BamWriter *writer = (*wi);
-    writer->object_destructs(this);
+  if (_bam_writers != (BamWriters *)NULL) {
+    BamWriters temp;
+    {
+      MutexHolder holder(_bam_writers_lock);
+      _bam_writers->swap(temp);
+      delete _bam_writers;
+      _bam_writers = NULL;
+    }
+    BamWriters::iterator wi;
+    for (wi = temp.begin(); wi != temp.end(); ++wi) {
+      BamWriter *writer = (*wi);
+      writer->object_destructs(this);
+    }
   }
   }
 }
 }
 
 

+ 6 - 4
panda/src/putil/typedWritable.h

@@ -22,6 +22,7 @@
 #include "typedObject.h"
 #include "typedObject.h"
 #include "vector_typedWritable.h"
 #include "vector_typedWritable.h"
 #include "pvector.h"
 #include "pvector.h"
+#include "pmutex.h"
 
 
 class BamReader;
 class BamReader;
 class BamWriter;
 class BamWriter;
@@ -56,11 +57,12 @@ protected:
   void fillin(DatagramIterator &scan, BamReader *manager);
   void fillin(DatagramIterator &scan, BamReader *manager);
 
 
 private:
 private:
-  // We need to store a list of the BamWriter(s) that have a reference
-  // to this object, so that we can remove the object from those
-  // tables when it destructs.
+  // We may need to store a list of the BamWriter(s) that have a
+  // reference to this object, so that we can remove the object from
+  // those tables when it destructs.
   typedef pvector<BamWriter *> BamWriters;
   typedef pvector<BamWriter *> BamWriters;
-  BamWriters _bam_writers;
+  BamWriters *_bam_writers;
+  static Mutex _bam_writers_lock;
 
 
 PUBLISHED:
 PUBLISHED:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {