Browse Source

SimpleHashMap

David Rose 18 years ago
parent
commit
15a52aa29f

+ 30 - 0
panda/src/pgraph/transformState.I

@@ -774,6 +774,36 @@ node_unref() const {
 #endif  // DO_PSTATS
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::get_composition_cache_size
+//       Access: Published
+//  Description: Returns the number of entries in the composition
+//               cache for this TransformState.  This is the number of
+//               other TransformStates whose composition with this one
+//               has been cached.  This number is not useful for any
+//               practical reason other than performance analysis.
+////////////////////////////////////////////////////////////////////
+INLINE int TransformState::
+get_composition_cache_size() const {
+  ReMutexHolder holder(*_states_lock);
+  return _composition_cache.get_num_entries();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::get_invert_composition_cache_size
+//       Access: Published
+//  Description: Returns the number of entries in the
+//               invert_composition cache for this TransformState.
+//               This is similar to the composition cache, but it
+//               records cache entries for the invert_compose()
+//               operation.  See get_composition_cache_size().
+////////////////////////////////////////////////////////////////////
+INLINE int TransformState::
+get_invert_composition_cache_size() const {
+  ReMutexHolder holder(*_states_lock);
+  return _invert_composition_cache.get_num_entries();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformState::flush_level
 //       Access: Public, Static

+ 232 - 96
panda/src/pgraph/transformState.cxx

@@ -28,6 +28,7 @@
 #include "reMutexHolder.h"
 #include "mutexHolder.h"
 #include "thread.h"
+#include "clockObject.h"
 
 ReMutex *TransformState::_states_lock = NULL;
 TransformState::States *TransformState::_states = NULL;
@@ -46,6 +47,71 @@ PStatCollector TransformState::_cache_counter("TransformStates:Cached");
 
 TypeHandle TransformState::_type_handle;
 
+class CacheStats {
+public:
+  void init();
+  void reset(double now);
+  void write(ostream &out) const;
+  void maybe_report();
+
+  int _cache_hits;
+  int _cache_semi_hits;
+  int _cache_misses;
+  int _cache_adds;
+  int _cache_new_adds;
+  int _cache_dels;
+  int _total_cache_size;
+  int _num_states;
+  double _last_reset;
+};
+static CacheStats _cache_stats;
+
+void CacheStats::
+init() {
+  reset(ClockObject::get_global_clock()->get_real_time());
+  _total_cache_size = 0;
+  _num_states = 0;
+}
+
+void CacheStats::
+reset(double now) {
+  _cache_hits = 0;
+  _cache_semi_hits = 0;
+  _cache_misses = 0;
+  _cache_adds = 0;
+  _cache_new_adds = 0;
+  _cache_dels = 0;
+  _last_reset = now;
+}
+
+void CacheStats::
+write(ostream &out) const {
+  out << "TransformState cache: " << _cache_hits << " hits, " 
+      << _cache_semi_hits << " semi-hits, "
+      << _cache_misses << " misses\n";
+  out << _cache_adds + _cache_new_adds << "(" << _cache_new_adds << ") adds(new), "
+      << _cache_dels << " dels, "
+      << (double)_total_cache_size / (double)_num_states 
+      << " average cache size\n";
+}
+
+static ConfigVariableBool cache_report("cache-report", false);
+static ConfigVariableDouble cache_report_interval("cache-report-interval", 5.0);
+
+void CacheStats::
+maybe_report() {
+  if (cache_report) {
+    double now = ClockObject::get_global_clock()->get_real_time();
+    if (now - _last_reset < cache_report_interval) {
+      return;
+    }
+    write(Notify::out());
+    reset(now);
+  }
+}
+
+
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformState::Constructor
 //       Access: Protected
@@ -61,6 +127,7 @@ TransformState() : _lock("TransformState") {
   _saved_entry = _states->end();
   _flags = F_is_identity | F_singular_known | F_is_2d;
   _inv_mat = (LMatrix4f *)NULL;
+  _cache_stats._num_states++;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -105,11 +172,12 @@ TransformState::
 
   // unref() should have cleared these.
   nassertv(_saved_entry == _states->end());
-  nassertv(_composition_cache.empty() && _invert_composition_cache.empty());
+  nassertv(_composition_cache.is_empty() && _invert_composition_cache.is_empty());
 
   // If this was true at the beginning of the destructor, but is no
   // longer true now, probably we've been double-deleted.
   nassertv(get_ref_count() == 0);
+  _cache_stats._num_states--;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -602,25 +670,29 @@ compose(const TransformState *other) const {
   ReMutexHolder holder(*_states_lock);
 
   // Is this composition already cached?
-  CompositionCache::const_iterator ci = _composition_cache.find(other);
-  if (ci != _composition_cache.end()) {
-    const Composition &comp = (*ci).second;
+  int index = _composition_cache.find(other);
+  if (index != -1) {
+    Composition &comp = ((TransformState *)this)->_composition_cache.modify_data(index);
     if (comp._result == (const TransformState *)NULL) {
       // Well, it wasn't cached already, but we already had an entry
       // (probably created for the reverse direction), so use the same
       // entry to store the new result.
       CPT(TransformState) result = do_compose(other);
-      ((Composition &)comp)._result = result;
+      comp._result = result;
 
       if (result != (const TransformState *)this) {
         // See the comments below about the need to up the reference
         // count only when the result is not the same as this.
         result->cache_ref();
       }
+      ++_cache_stats._cache_semi_hits;
+    } else {
+      ++_cache_stats._cache_hits;
     }
     // Here's the cache!
     return comp._result;
   }
+  ++_cache_stats._cache_misses;
 
   // We need to make a new cache entry, both in this object and in the
   // other object.  We make both records so the other TransformState
@@ -631,10 +703,25 @@ compose(const TransformState *other) const {
   // result; the other will be NULL for now.
   CPT(TransformState) result = do_compose(other);
 
-  // Order is important here, in case other == this.
-  ((TransformState *)other)->_composition_cache[this]._result = NULL;
+  ++_cache_stats._total_cache_size;
+  if (_composition_cache.get_size() == 0) {
+    ++_cache_stats._cache_new_adds;
+  } else {
+    ++_cache_stats._cache_adds;
+  }
+
   ((TransformState *)this)->_composition_cache[other]._result = result;
 
+  if (other != this) {
+    ++_cache_stats._total_cache_size;
+    if (other->_composition_cache.get_size() == 0) {
+      ++_cache_stats._cache_new_adds;
+    } else {
+      ++_cache_stats._cache_adds;
+    }
+    ((TransformState *)other)->_composition_cache[this]._result = NULL;
+  }
+
   if (result != (const TransformState *)this) {
     // If the result of compose() is something other than this,
     // explicitly increment the reference count.  We have to be sure
@@ -647,6 +734,8 @@ compose(const TransformState *other) const {
     // that would be a self-referential leak.)
   }
 
+  _cache_stats.maybe_report();
+
   return result;
 }
 
@@ -696,25 +785,29 @@ invert_compose(const TransformState *other) const {
   ReMutexHolder holder(*_states_lock);
 
   // Is this composition already cached?
-  CompositionCache::const_iterator ci = _invert_composition_cache.find(other);
-  if (ci != _invert_composition_cache.end()) {
-    const Composition &comp = (*ci).second;
+  int index = _invert_composition_cache.find(other);
+  if (index != -1) {
+    Composition &comp = ((TransformState *)this)->_invert_composition_cache.modify_data(index);
     if (comp._result == (const TransformState *)NULL) {
       // Well, it wasn't cached already, but we already had an entry
       // (probably created for the reverse direction), so use the same
       // entry to store the new result.
       CPT(TransformState) result = do_invert_compose(other);
-      ((Composition &)comp)._result = result;
+      comp._result = result;
 
       if (result != (const TransformState *)this) {
         // See the comments below about the need to up the reference
         // count only when the result is not the same as this.
         result->cache_ref();
       }
+      ++_cache_stats._cache_semi_hits;
+    } else {
+      ++_cache_stats._cache_hits;
     }
     // Here's the cache!
     return comp._result;
   }
+  ++_cache_stats._cache_misses;
 
   // We need to make a new cache entry, both in this object and in the
   // other object.  We make both records so the other TransformState
@@ -725,9 +818,24 @@ invert_compose(const TransformState *other) const {
   // result; the other will be NULL for now.
   CPT(TransformState) result = do_invert_compose(other);
 
-  ((TransformState *)other)->_invert_composition_cache[this]._result = NULL;
+  ++_cache_stats._total_cache_size;
+  if (_invert_composition_cache.get_size() == 0) {
+    ++_cache_stats._cache_new_adds;
+  } else {
+    ++_cache_stats._cache_adds;
+  }
   ((TransformState *)this)->_invert_composition_cache[other]._result = result;
 
+  if (other != this) {
+    ++_cache_stats._total_cache_size;
+    if (other->_invert_composition_cache.get_size() == 0) {
+      ++_cache_stats._cache_new_adds;
+    } else {
+      ++_cache_stats._cache_adds;
+    }
+    ((TransformState *)other)->_invert_composition_cache[this]._result = NULL;
+  }
+
   if (result != (const TransformState *)this) {
     // If the result of compose() is something other than this,
     // explicitly increment the reference count.  We have to be sure
@@ -905,6 +1013,8 @@ output(ostream &out) const {
 void TransformState::
 write(ostream &out, int indent_level) const {
   indent(out, indent_level) << *this << "\n";
+  indent(out, indent_level + 2) << _composition_cache << "\n";
+  indent(out, indent_level + 2) << _invert_composition_cache << "\n";
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -958,32 +1068,34 @@ get_num_unused_states() {
   for (si = _states->begin(); si != _states->end(); ++si) {
     const TransformState *state = (*si);
 
-    CompositionCache::const_iterator ci;
-    for (ci = state->_composition_cache.begin();
-         ci != state->_composition_cache.end();
-         ++ci) {
-      const TransformState *result = (*ci).second._result;
-      if (result != (const TransformState *)NULL && result != state) {
-        // Here's a TransformState that's recorded in the cache.
-        // Count it.
-        pair<StateCount::iterator, bool> ir =
-          state_count.insert(StateCount::value_type(result, 1));
-        if (!ir.second) {
-          // If the above insert operation fails, then it's already in
-          // the cache; increment its value.
-          (*(ir.first)).second++;
+    int i;
+    int cache_size = state->_composition_cache.get_size();
+    for (i = 0; i < cache_size; ++i) {
+      if (state->_composition_cache.has_element(i)) {
+        const TransformState *result = state->_composition_cache.get_data(i)._result;
+        if (result != (const TransformState *)NULL && result != state) {
+          // Here's a TransformState that's recorded in the cache.
+          // Count it.
+          pair<StateCount::iterator, bool> ir =
+            state_count.insert(StateCount::value_type(result, 1));
+          if (!ir.second) {
+            // If the above insert operation fails, then it's already in
+            // the cache; increment its value.
+            (*(ir.first)).second++;
+          }
         }
       }
     }
-    for (ci = state->_invert_composition_cache.begin();
-         ci != state->_invert_composition_cache.end();
-         ++ci) {
-      const TransformState *result = (*ci).second._result;
-      if (result != (const TransformState *)NULL && result != state) {
-        pair<StateCount::iterator, bool> ir =
-          state_count.insert(StateCount::value_type(result, 1));
-        if (!ir.second) {
-          (*(ir.first)).second++;
+    cache_size = state->_invert_composition_cache.get_size();
+    for (i = 0; i < cache_size; ++i) {
+      if (state->_invert_composition_cache.has_element(i)) {
+        const TransformState *result = state->_invert_composition_cache.get_data(i)._result;
+        if (result != (const TransformState *)NULL && result != state) {
+          pair<StateCount::iterator, bool> ir =
+            state_count.insert(StateCount::value_type(result, 1));
+          if (!ir.second) {
+            (*(ir.first)).second++;
+          }
         }
       }
     }
@@ -1064,27 +1176,31 @@ clear_cache() {
     for (ti = temp_states.begin(); ti != temp_states.end(); ++ti) {
       TransformState *state = (TransformState *)(*ti).p();
 
-      CompositionCache::const_iterator ci;
-      for (ci = state->_composition_cache.begin();
-           ci != state->_composition_cache.end();
-           ++ci) {
-        const TransformState *result = (*ci).second._result;
-        if (result != (const TransformState *)NULL && result != state) {
-          result->cache_unref();
-          nassertr(result->get_ref_count() > 0, 0);
+      int i;
+      int cache_size = state->_composition_cache.get_size();
+      for (i = 0; i < cache_size; ++i) {
+        if (state->_composition_cache.has_element(i)) {
+          const TransformState *result = state->_composition_cache.get_data(i)._result;
+          if (result != (const TransformState *)NULL && result != state) {
+            result->cache_unref();
+            nassertr(result->get_ref_count() > 0, 0);
+          }
         }
       }
+      _cache_stats._total_cache_size -= state->_composition_cache.get_num_entries();
       state->_composition_cache.clear();
 
-      for (ci = state->_invert_composition_cache.begin();
-           ci != state->_invert_composition_cache.end();
-           ++ci) {
-        const TransformState *result = (*ci).second._result;
-        if (result != (const TransformState *)NULL && result != state) {
-          result->cache_unref();
-          nassertr(result->get_ref_count() > 0, 0);
+      cache_size = state->_invert_composition_cache.get_size();
+      for (i = 0; i < cache_size; ++i) {
+        if (state->_invert_composition_cache.has_element(i)) {
+          const TransformState *result = state->_invert_composition_cache.get_data(i)._result;
+          if (result != (const TransformState *)NULL && result != state) {
+            result->cache_unref();
+            nassertr(result->get_ref_count() > 0, 0);
+          }
         }
       }
+      _cache_stats._total_cache_size -= state->_invert_composition_cache.get_num_entries();
       state->_invert_composition_cache.clear();
     }
 
@@ -1260,6 +1376,7 @@ init_states() {
   // called at static init time, presumably when there is still only
   // one thread in the world.
   _states_lock = new ReMutex("TransformState::_states_lock");
+  _cache_stats.init();
   nassertv(Thread::get_current_thread() == Thread::get_main_thread());
 }
   
@@ -1558,38 +1675,42 @@ r_detect_cycles(const TransformState *start_state,
     return (current_state == start_state && length > 2);
   }
   ((TransformState *)current_state)->_cycle_detect = this_seq;
-    
-  CompositionCache::const_iterator ci;
-  for (ci = current_state->_composition_cache.begin();
-       ci != current_state->_composition_cache.end();
-       ++ci) {
-    const TransformState *result = (*ci).second._result;
-    if (result != (const TransformState *)NULL) {
-      if (r_detect_cycles(start_state, result, length + 1, 
-                          this_seq, cycle_desc)) {
-        // Cycle detected.
-        if (cycle_desc != (CompositionCycleDesc *)NULL) {
-          CompositionCycleDescEntry entry((*ci).first, result, false);
-          cycle_desc->push_back(entry);
+
+  int i;
+  int cache_size = current_state->_composition_cache.get_size();
+  for (i = 0; i < cache_size; ++i) {
+    if (current_state->_composition_cache.has_element(i)) {
+      const TransformState *result = current_state->_composition_cache.get_data(i)._result;
+      if (result != (const TransformState *)NULL) {
+        if (r_detect_cycles(start_state, result, length + 1, 
+                            this_seq, cycle_desc)) {
+          // Cycle detected.
+          if (cycle_desc != (CompositionCycleDesc *)NULL) {
+            const TransformState *other = current_state->_composition_cache.get_key(i);
+            CompositionCycleDescEntry entry(other, result, false);
+            cycle_desc->push_back(entry);
+          }
+          return true;
         }
-        return true;
       }
     }
   }
 
-  for (ci = current_state->_invert_composition_cache.begin();
-       ci != current_state->_invert_composition_cache.end();
-       ++ci) {
-    const TransformState *result = (*ci).second._result;
-    if (result != (const TransformState *)NULL) {
-      if (r_detect_cycles(start_state, result, length + 1,
-                          this_seq, cycle_desc)) {
-        // Cycle detected.
-        if (cycle_desc != (CompositionCycleDesc *)NULL) {
-          CompositionCycleDescEntry entry((*ci).first, result, true);
-          cycle_desc->push_back(entry);
+  cache_size = current_state->_invert_composition_cache.get_size();
+  for (i = 0; i < cache_size; ++i) {
+    if (current_state->_invert_composition_cache.has_element(i)) {
+      const TransformState *result = current_state->_invert_composition_cache.get_data(i)._result;
+      if (result != (const TransformState *)NULL) {
+        if (r_detect_cycles(start_state, result, length + 1,
+                            this_seq, cycle_desc)) {
+          // Cycle detected.
+          if (cycle_desc != (CompositionCycleDesc *)NULL) {
+            const TransformState *other = current_state->_invert_composition_cache.get_key(i);
+            CompositionCycleDescEntry entry(other, result, true);
+            cycle_desc->push_back(entry);
+          }
+          return true;
         }
-        return true;
       }
     }
   }
@@ -1650,7 +1771,7 @@ remove_cache_pointers() {
   // it.
 
 #ifdef DO_PSTATS
-  if (_composition_cache.empty() && _invert_composition_cache.empty()) {
+  if (_composition_cache.is_empty() && _invert_composition_cache.is_empty()) {
     return;
   }
   PStatTimer timer(_cache_update_pcollector);
@@ -1658,8 +1779,12 @@ remove_cache_pointers() {
 
   // There are lots of ways to do this loop wrong.  Be very careful if
   // you need to modify it for any reason.
-  while (!_composition_cache.empty()) {
-    CompositionCache::iterator ci = _composition_cache.begin();
+  int i = 0;
+  while (!_composition_cache.is_empty()) {
+    // Scan for the next used slot in the table.
+    while (!_composition_cache.has_element(i)) {
+      ++i;
+    }
 
     // It is possible that the "other" TransformState object is
     // currently within its own destructor.  We therefore can't use a
@@ -1668,29 +1793,33 @@ remove_cache_pointers() {
     // reference count to ensure it doesn't destruct while we process
     // this loop; as long as we ensure that no *other* TransformState
     // objects destruct, there will be no reason for that one to.
-    TransformState *other = (TransformState *)(*ci).first;
+    TransformState *other = (TransformState *)_composition_cache.get_key(i);
 
     // We hold a copy of the composition result so we can dereference
     // it later.
-    Composition comp = (*ci).second;
+    Composition comp = _composition_cache.get_data(i);
 
     // Now we can remove the element from our cache.  We do this now,
     // rather than later, before any other TransformState objects have
     // had a chance to destruct, so we are confident that our iterator
     // is still valid.
-    _composition_cache.erase(ci);
+    _composition_cache.remove_element(i);
+    --_cache_stats._total_cache_size;
+    ++_cache_stats._cache_dels;
 
     if (other != this) {
-      CompositionCache::iterator oci = other->_composition_cache.find(this);
+      int oi = other->_composition_cache.find(this);
 
       // We may or may not still be listed in the other's cache (it
       // might be halfway through pulling entries out, from within its
       // own destructor).
-      if (oci != other->_composition_cache.end()) {
+      if (oi != -1) {
         // Hold a copy of the other composition result, too.
-        Composition ocomp = (*oci).second;
+        Composition ocomp = other->_composition_cache.get_data(oi);
         
-        other->_composition_cache.erase(oci);
+        other->_composition_cache.remove_element(oi);
+        --_cache_stats._total_cache_size;
+        ++_cache_stats._cache_dels;
         
         // It's finally safe to let our held pointers go away.  This may
         // have cascading effects as other TransformState objects are
@@ -1710,18 +1839,25 @@ remove_cache_pointers() {
   }
 
   // A similar bit of code for the invert cache.
-  while (!_invert_composition_cache.empty()) {
-    CompositionCache::iterator ci = _invert_composition_cache.begin();
-    TransformState *other = (TransformState *)(*ci).first;
+  i = 0;
+  while (!_invert_composition_cache.is_empty()) {
+    while (!_invert_composition_cache.has_element(i)) {
+      ++i;
+    }
+
+    TransformState *other = (TransformState *)_invert_composition_cache.get_key(i);
     nassertv(other != this);
-    Composition comp = (*ci).second;
-    _invert_composition_cache.erase(ci);
+    Composition comp = _invert_composition_cache.get_data(i);
+    _invert_composition_cache.remove_element(i);
+    --_cache_stats._total_cache_size;
+    ++_cache_stats._cache_dels;
     if (other != this) {
-      CompositionCache::iterator oci = 
-        other->_invert_composition_cache.find(this);
-      if (oci != other->_invert_composition_cache.end()) {
-        Composition ocomp = (*oci).second;
-        other->_invert_composition_cache.erase(oci);
+      int oi = other->_invert_composition_cache.find(this);
+      if (oi != -1) {
+        Composition ocomp = other->_invert_composition_cache.get_data(oi);
+        other->_invert_composition_cache.remove_element(oi);
+        --_cache_stats._total_cache_size;
+        ++_cache_stats._cache_dels;
         if (ocomp._result != (const TransformState *)NULL && ocomp._result != other) {
           cache_unref_delete(ocomp._result);
         }

+ 11 - 6
panda/src/pgraph/transformState.h

@@ -33,6 +33,7 @@
 #include "pmutex.h"
 #include "config_pgraph.h"
 #include "deletedChain.h"
+#include "simpleHashMap.h"
 
 class GraphicsStateGuardianBase;
 class FactoryParams;
@@ -179,6 +180,9 @@ PUBLISHED:
   INLINE void node_ref() const;
   INLINE bool node_unref() const;
 
+  INLINE int get_composition_cache_size() const;
+  INLINE int get_invert_composition_cache_size() const;
+
   void output(ostream &out) const;
   void write(ostream &out, int indent_level) const;
 
@@ -240,6 +244,11 @@ private:
   // remove the entry if *either* of the input TransformStates destructs.
   // To implement this, we always record Composition entries in pairs,
   // one in each of the two involved TransformState objects.
+    
+  // The first element of the map is the object we compose with.  This
+  // is not reference counted within this map; instead we store a
+  // companion pointer in the other object, and remove the references
+  // explicitly when either object destructs.
   class Composition {
   public:
     INLINE Composition();
@@ -249,12 +258,8 @@ private:
     // pointer as this.
     const TransformState *_result;
   };
-    
-  // The first element of the map is the object we compose with.  This
-  // is not reference counted within this map; instead we store a
-  // companion pointer in the other object, and remove the references
-  // explicitly when either object destructs.
-  typedef phash_map<const TransformState *, Composition, pointer_hash> CompositionCache;
+
+  typedef SimpleHashMap<const TransformState *, Composition, pointer_hash> CompositionCache;
   CompositionCache _composition_cache;
   CompositionCache _invert_composition_cache;
 

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

@@ -54,6 +54,7 @@
     portalMask.h \
     pta_double.h \
     pta_float.h pta_int.h \
+    simpleHashMap.I simpleHashMap.h \
     sparseArray.I sparseArray.h \
     string_utils.I string_utils.N string_utils.h \
     stringStreamBuf.I stringStreamBuf.h \
@@ -99,6 +100,7 @@
     nodeCachedReferenceCount.cxx \
     pta_double.cxx pta_float.cxx \
     pta_int.cxx pta_ushort.cxx \
+    simpleHashMap.cxx \
     sparseArray.cxx \
     string_utils.cxx \
     stringStreamBuf.cxx \
@@ -156,6 +158,7 @@
     portalMask.h \
     pta_double.h \
     pta_float.h pta_int.h pta_ushort.h \
+    simpleHashMap.I simpleHashMap.h \
     sparseArray.I sparseArray.h \
     string_utils.I string_utils.h \
     stringStreamBuf.I stringStreamBuf.h \

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

@@ -12,6 +12,7 @@
 #include "pta_float.cxx"
 #include "pta_int.cxx"
 #include "pta_ushort.cxx"
+#include "simpleHashMap.cxx"
 #include "sparseArray.cxx"
 #include "string_utils.cxx"
 #include "stringStreamBuf.cxx"

+ 620 - 0
panda/src/putil/simpleHashMap.I

@@ -0,0 +1,620 @@
+// Filename: simpleHashMap.I
+// Created by:  drose (19Jul07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: SimpleHashMap::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE SimpleHashMap<Key, Value, Compare>::
+SimpleHashMap(const Compare &comp) :
+  _table(NULL),
+  _deleted_chain(NULL),
+  _table_size(0),
+  _num_entries(0),
+  _comp(comp)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE SimpleHashMap<Key, Value, Compare>::
+~SimpleHashMap() {
+  clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::swap
+//       Access: Public
+//  Description: Quickly exchanges the contents of this map and the
+//               other map.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE void SimpleHashMap<Key, Value, Compare>::
+swap(SimpleHashMap<Key, Value, Compare> &other) {
+  TableEntry *t0 = _table;
+  _table = other._table;
+  other._table = t0;
+
+  DeletedBufferChain *t1 = _deleted_chain;
+  _deleted_chain = other._deleted_chain;
+  other._deleted_chain = t1;
+
+  size_t t2 = _table_size;
+  _table_size = other._table_size;
+  other._table_size = t2;
+
+  size_t t3 = _num_entries;
+  _num_entries = other._num_entries;
+  other._num_entries = t3;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::find
+//       Access: Public
+//  Description: Searches for the indicated key in the table.  Returns
+//               its index number if it is found, or -1 if it is not
+//               present in the table.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+int SimpleHashMap<Key, Value, Compare>::
+find(const Key &key) const {
+  if (_table_size == 0) {
+    // Special case: the table is empty.
+    return -1;
+  }
+
+  size_t index = get_hash(key);
+  if (!has_element(index)) {
+    return -1;
+  }
+  if (is_element(index, key)) {
+    return index;
+  }
+
+  // There was some other key at the hashed slot.  That's a hash
+  // conflict.  Maybe our entry was recorded at a later slot position;
+  // scan the subsequent positions until we find the entry or an
+  // unused slot, indicating the end of the scan.
+  size_t i = index;
+  i = (i + 1) & (_table_size - 1);
+  while (i != index && has_element(i)) {
+    if (is_element(i, key)) {
+      return i;
+    }
+    i = (i + 1) & (_table_size - 1);
+  }
+
+  // The key is not in the table.
+  return -1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::store
+//       Access: Public
+//  Description: Records the indicated key/data pair in the map.  If
+//               the key was already present, silently replaces it.
+//               Returns a reference to the value in the map.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+Value &SimpleHashMap<Key, Value, Compare>::
+store(const Key &key, const Value &data) {
+  if (_table_size == 0) {
+    // Special case: the first key in an empty table.
+    nassertr(_num_entries == 0, _table[0]._data);
+    new_table();
+    size_t index = get_hash(key);
+    store_new_element(index, key, data);
+    ++_num_entries;
+    return _table[index]._data;
+  }
+
+  size_t index = get_hash(key);
+  if (!has_element(index)) {
+    if (consider_expand_table()) {
+      return store(key, data);
+    }
+    store_new_element(index, key, data);
+    ++_num_entries;
+    return _table[index]._data;
+  }
+  if (is_element(index, key)) {
+    _table[index]._data = data;
+    return _table[index]._data;
+  }
+
+  // There was some other key at the hashed slot.  That's a hash
+  // conflict.  Record this entry at a later position.
+  size_t i = index;
+  i = (i + 1) & (_table_size - 1);
+  while (i != index) {
+    if (!has_element(i)) {
+      if (consider_expand_table()) {
+        return store(key, data);
+      }
+      store_new_element(i, key, data);
+      ++_num_entries;
+      return _table[i]._data;
+    }
+    if (is_element(i, key)) {
+      _table[i]._data = data;
+      return _table[i]._data;
+    }
+    i = (i + 1) & (_table_size - 1);
+  }
+
+  // Shouldn't get here unless _num_entries == _table_size, which
+  // shouldn't be possible due to consider_expand_table().
+  nassertr(false, _table[0]._data);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::remove
+//       Access: Public
+//  Description: Removes the indicated key and its associated data
+//               from the table.  Returns true if the key was removed,
+//               false if it was not present.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE bool SimpleHashMap<Key, Value, Compare>::
+remove(const Key &key) {
+  int index = find(key);
+  if (index == -1) {
+    return false;
+  }
+  remove_element(index);
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::clear
+//       Access: Public
+//  Description: Completely empties the table.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+void SimpleHashMap<Key, Value, Compare>::
+clear() {
+  if (_table_size != 0) {
+    for (size_t i = 0; i < _table_size; ++i) {
+      if (has_element(i)) {
+        clear_element(i);
+      }
+    }
+
+    _deleted_chain->deallocate(_table, TypeHandle::none());
+    _table = NULL;
+    _deleted_chain = NULL;
+    _table_size = 0;
+    _num_entries = 0;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::operator []
+//       Access: Public
+//  Description: Returns a modifiable reference to the data associated
+//               with the indicated key, or creates a new data entry
+//               and returns its reference.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE Value &SimpleHashMap<Key, Value, Compare>::
+operator [] (const Key &key) {
+  int index = find(key);
+  if (index != -1) {
+    return modify_data(index);
+  }
+  return store(key, Value());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::get_size
+//       Access: Public
+//  Description: Returns the total number of slots in the table.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE int SimpleHashMap<Key, Value, Compare>::
+get_size() const {
+  return _table_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::has_element
+//       Access: Public
+//  Description: Returns true if there is an element stored in the nth
+//               slot, false otherwise.
+//
+//               n should be in the range 0 <= n < get_size().
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE bool SimpleHashMap<Key, Value, Compare>::
+has_element(int n) const {
+  nassertr(n >= 0 && n < (int)_table_size, false);
+  return get_exists_array()[n];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::get_key
+//       Access: Public
+//  Description: Returns the key in the nth slot of the table.
+//
+//               It is an error to call this if there is nothing
+//               stored in the nth slot (use has_element() to check
+//               this first).  n should be in the range 0 <= n <
+//               get_size().
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE const Key &SimpleHashMap<Key, Value, Compare>::
+get_key(int n) const {
+  nassertr(has_element(n), _table[n]._key);
+  return _table[n]._key;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::get_data
+//       Access: Public
+//  Description: Returns the data in the nth slot of the table.
+//
+//               It is an error to call this if there is nothing
+//               stored in the nth slot (use has_element() to check
+//               this first).  n should be in the range 0 <= n <
+//               get_size().
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE const Value &SimpleHashMap<Key, Value, Compare>::
+get_data(int n) const {
+  nassertr(has_element(n), _table[n]._data);
+  return _table[n]._data;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::modify_data
+//       Access: Public
+//  Description: Returns a modifiable reference to the data in the nth
+//               slot of the table.
+//
+//               It is an error to call this if there is nothing
+//               stored in the nth slot (use has_element() to check
+//               this first).  n should be in the range 0 <= n <
+//               get_size().
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE Value &SimpleHashMap<Key, Value, Compare>::
+modify_data(int n) {
+  nassertr(has_element(n), _table[n]._data);
+  return _table[n]._data;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::set_data
+//       Access: Public
+//  Description: Changes the data for the nth slot of the table.
+//
+//               It is an error to call this if there is nothing
+//               stored in the nth slot (use has_element() to check
+//               this first).  n should be in the range 0 <= n <
+//               get_size().
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE void SimpleHashMap<Key, Value, Compare>::
+set_data(int n, const Value &data) {
+  nassertv(has_element(n));
+  _table[n]._data = data;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::remove_element
+//       Access: Public
+//  Description: Removes the nth slot from the table.
+//
+//               It is an error to call this if there is nothing
+//               stored in the nth slot (use has_element() to check
+//               this first).  n should be in the range 0 <= n <
+//               get_size().
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+void SimpleHashMap<Key, Value, Compare>::
+remove_element(int n) {
+  nassertv(has_element(n));
+
+  clear_element(n);
+  nassertv(_num_entries > 0);
+  --_num_entries;
+
+  // Now we have put a hole in the table.  If there was a hash
+  // conflict in the slot following this one, we have to move it down
+  // to close the hole.
+  size_t i = n;
+  i = (i + 1) & (_table_size - 1);
+  while (has_element(i)) {
+    size_t wants_index = get_hash(_table[i]._key);
+    if (wants_index != i) {
+      // This one was a hash conflict; try to put it where it belongs.
+      // We can't just put it in n, since maybe it belongs somewhere
+      // after n.
+      while (wants_index != i && has_element(wants_index)) {
+        wants_index = (wants_index + 1) & (_table_size - 1);
+      }
+      if (wants_index != i) {
+        store_new_element(wants_index, _table[i]._key, _table[i]._data);
+        clear_element(i);
+      }
+    }
+
+    // Continue until we encounter the next unused slot.  Until we do,
+    // we can't be sure we've found all of the potential hash
+    // conflicts.
+    i = (i + 1) & (_table_size - 1);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::get_num_entries
+//       Access: Public
+//  Description: Returns the number of active entries in the table.
+//               This is not necessarily related to the number of
+//               slots in the table as reported by get_size().  Use
+//               get_size() to iterate through all of the slots, not
+//               get_num_entries().
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE int SimpleHashMap<Key, Value, Compare>::
+get_num_entries() const {
+  return _num_entries;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::is_empty
+//       Access: Public
+//  Description: Returns true if the table is empty;
+//               i.e. get_num_entries() == 0.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE bool SimpleHashMap<Key, Value, Compare>::
+is_empty() const {
+  return (_num_entries == 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::output
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+void SimpleHashMap<Key, Value, Compare>::
+output(ostream &out) const {
+  out << "SimpleHashMap (" << _num_entries << " entries): [";
+  for (size_t i = 0; i < _table_size; ++i) {
+    if (!has_element(i)) {
+      out << " *";
+
+    } else {
+      out << " " << _table[i]._key;
+      size_t index = get_hash(_table[i]._key);
+      if (index != i) {
+        // This was misplaced as the result of a hash conflict.
+        // Report how far off it is.
+        out << "(" << ((_table_size + i - index) & (_table_size - 1)) << ")";
+      }
+    }
+  }
+  out << " ]";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::write
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+void SimpleHashMap<Key, Value, Compare>::
+write(ostream &out) const {
+  output(out);
+  out << "\n";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::validate
+//       Access: Public
+//  Description: Returns true if the internal table appears to be
+//               consistent, false if there are some internal errors.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+bool SimpleHashMap<Key, Value, Compare>::
+validate() const {
+  size_t count = 0;
+
+  for (size_t i = 0; i < _table_size; ++i) {
+    if (has_element(i)) {
+      ++count;
+      size_t ideal_index = get_hash(_table[i]._key);
+      size_t wants_index = ideal_index;
+      while (wants_index != i && has_element(wants_index)) {
+        wants_index = (wants_index + 1) & (_table_size - 1);
+      }
+      if (wants_index != i) {
+        util_cat.error()
+          << "SimpleHashMap is invalid: key " << _table[i]._key
+          << " should be in slot " << wants_index << " instead of "
+          << i << " (ideal is " << ideal_index << ")\n";
+        write(util_cat.error(false));
+        return false;
+      }
+    }
+  }
+
+  if (count != _num_entries) {
+    util_cat.error()
+      << "SimpleHashMap is invalid: reports " << _num_entries
+      << " entries, actually has " << count << "\n";
+    write(util_cat.error(false));
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::get_hash
+//       Access: Private
+//  Description: Computes an appropriate index number to store the
+//               given pointer.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE size_t SimpleHashMap<Key, Value, Compare>::
+get_hash(const Key &key) const {
+  /*
+  // We want a hash constant 0 < k < 1.  This one is suggested by
+  // Knuth:
+  static const double hash_constant = (sqrt(5.0) - 1.0) / 2.0;
+  double f = ((double)_comp(key) * hash_constant);
+  f -= floor(f);
+  return (size_t)floor(f * _table_size);
+  */
+
+  return ((_comp(key) * (size_t)9973) >> 8) & (_table_size - 1);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::is_element
+//       Access: Private
+//  Description: Returns true if element n matches key.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE bool SimpleHashMap<Key, Value, Compare>::
+is_element(int n, const Key &key) const {
+  nassertr(has_element(n), false);
+  return _comp.is_equal(_table[n]._key, key);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::store_new_element
+//       Access: Private
+//  Description: Constructs a new TableEntry at position n, storing
+//               the indicated key and value.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE void SimpleHashMap<Key, Value, Compare>::
+store_new_element(int n, const Key &key, const Value &data) {
+  new(&_table[n]) TableEntry(key, data);
+  get_exists_array()[n] = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::clear_element
+//       Access: Private
+//  Description: Destructs the TableEntry at position n.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE void SimpleHashMap<Key, Value, Compare>::
+clear_element(int n) {
+  _table[n].~TableEntry();
+  get_exists_array()[n] = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::get_exists_array
+//       Access: Private
+//  Description: Returns the beginning of the array of _table_size
+//               unsigned chars that are the boolean flags for whether
+//               each element exists (has been constructed) within the
+//               table.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE unsigned char *SimpleHashMap<Key, Value, Compare>::
+get_exists_array() const {
+  return (unsigned char *)(_table + _table_size);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::new_table
+//       Access: Private
+//  Description: Allocates a brand new table.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+void SimpleHashMap<Key, Value, Compare>::
+new_table() {
+  nassertv(_table_size == 0 && _num_entries == 0);
+
+  // Pick a good initial table size.  For now, we make it as small as
+  // possible.  Maybe that's the right answer.
+  _table_size = 2;
+
+  // We allocate enough bytes for _table_size elements of TableEntry,
+  // plus _table_size more bytes at the end (for the exists array).
+  size_t alloc_size = _table_size * sizeof(TableEntry) + _table_size;
+
+  _deleted_chain = memory_hook->get_deleted_chain(alloc_size);
+  _table = (TableEntry *)_deleted_chain->allocate(alloc_size, TypeHandle::none());
+  memset(get_exists_array(), 0, _table_size);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::consider_expand_table
+//       Access: Private
+//  Description: Expands the table if it will need it (assuming one
+//               more element is about to be added).  Returns true if
+//               expanded, false otherwise.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+INLINE bool SimpleHashMap<Key, Value, Compare>::
+consider_expand_table() {
+  if (_num_entries >= (_table_size >> 1)) {
+    expand_table();
+    return true;
+  }
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SimpleHashMap::expand_table
+//       Access: Private
+//  Description: Doubles the size of the existing table.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare>
+void SimpleHashMap<Key, Value, Compare>::
+expand_table() {
+  nassertv(_table_size != 0);
+
+  SimpleHashMap<Key, Value, Compare> old_map(_comp);
+  swap(old_map);
+
+  _table_size = (old_map._table_size << 1);
+  nassertv(_table == NULL);
+
+  // We allocate enough bytes for _table_size elements of TableEntry,
+  // plus _table_size more bytes at the end (for the exists array).
+  size_t alloc_size = _table_size * sizeof(TableEntry) + _table_size;
+  _deleted_chain = memory_hook->get_deleted_chain(alloc_size);
+  _table = (TableEntry *)_deleted_chain->allocate(alloc_size, TypeHandle::none());
+  memset(get_exists_array(), 0, _table_size);
+
+  // Now copy the entries from the old table into the new table.
+  for (size_t i = 0; i < old_map._table_size; ++i) {
+    if (old_map.has_element(i)) {
+      store(old_map._table[i]._key, old_map._table[i]._data);
+    }
+  }
+
+  nassertv(old_map._num_entries == _num_entries);
+}

+ 19 - 0
panda/src/putil/simpleHashMap.cxx

@@ -0,0 +1,19 @@
+// Filename: simpleHashMap.cxx
+// Created by:  drose (19Jul07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "simpleHashMap.h"

+ 116 - 0
panda/src/putil/simpleHashMap.h

@@ -0,0 +1,116 @@
+// Filename: simpleHashMap.h
+// Created by:  drose (19Jul07)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 SIMPLEHASHMAP_H
+#define SIMPLEHASHMAP_H
+
+#include "pandabase.h"
+#include "pvector.h"
+#include "config_util.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : SimpleHashMap
+// Description : This template class implements an unordered map of
+//               keys to data, implemented as a hashtable.  It is
+//               similar to STL's hash_map, but (a) it has a simpler
+//               interface (we don't mess around with iterators), (b)
+//               it wants an additional method on the Compare object,
+//               Compare::is_equal(a, b), and (c) it doesn't depend on
+//               the system STL providing hash_map.
+//
+//               The map uses a void * as a unique key, which is
+//               associated with another void * as data.  You may pass
+//               any pointer other than NULL as the key; you may pass
+//               any pointer (including NULL) as the data.  Only one
+//               entry is stored per each different key pointer.
+////////////////////////////////////////////////////////////////////
+template<class Key, class Value, class Compare = method_hash<Key, less<Key> > >
+class EXPCL_PANDA_PUTIL SimpleHashMap {
+public:
+#ifndef CPPPARSER
+  INLINE SimpleHashMap(const Compare &comp = Compare());
+  INLINE ~SimpleHashMap();
+
+  INLINE void swap(SimpleHashMap &other);
+
+  int find(const Key &key) const;
+  Value &store(const Key &key, const Value &data);
+  INLINE bool remove(const Key &key);
+  void clear();
+
+  INLINE Value &operator [] (const Key &key);
+
+  INLINE int get_size() const;
+  INLINE bool has_element(int n) const;
+  INLINE const Key &get_key(int n) const;
+  INLINE const Value &get_data(int n) const;
+  INLINE Value &modify_data(int n);
+  INLINE void set_data(int n, const Value &data);
+  void remove_element(int n);
+
+  INLINE int get_num_entries() const;
+  INLINE bool is_empty() const;
+
+  void output(ostream &out) const;
+  void write(ostream &out) const;
+  bool validate() const;
+
+private:
+  class TableEntry;
+
+  INLINE size_t get_hash(const Key &key) const;
+
+  INLINE bool is_element(int n, const Key &key) const;
+  INLINE void store_new_element(int n, const Key &key, const Value &data);
+  INLINE void clear_element(int n);
+  INLINE unsigned char *get_exists_array() const;
+
+  void new_table();
+  INLINE bool consider_expand_table();
+  void expand_table();
+
+  class TableEntry {
+  public:
+    INLINE TableEntry(const Key &key, const Value &data) :
+      _key(key),
+      _data(data) {}
+    Key _key;
+    Value _data;
+  };
+
+  TableEntry *_table;
+  DeletedBufferChain *_deleted_chain;
+  size_t _table_size;
+  size_t _num_entries;
+
+  Compare _comp;
+#endif  // CPPPARSER
+};
+
+template<class Key, class Value, class Compare>
+inline ostream &operator << (ostream &out, const SimpleHashMap<Key, Value, Compare> &shm) {
+  shm.output(out);
+  return out;
+}
+
+#ifndef CPPPARSER
+#include "simpleHashMap.I"
+#endif  // CPPPARSER
+
+#endif
+