diff --git a/panda/src/pgraph/transformState.I b/panda/src/pgraph/transformState.I index 366a12a909..6815d9d254 100644 --- a/panda/src/pgraph/transformState.I +++ b/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 diff --git a/panda/src/pgraph/transformState.cxx b/panda/src/pgraph/transformState.cxx index cef57b9965..2b0d14fe1f 100644 --- a/panda/src/pgraph/transformState.cxx +++ b/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 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 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 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 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); } diff --git a/panda/src/pgraph/transformState.h b/panda/src/pgraph/transformState.h index f18c1661b5..1923874076 100644 --- a/panda/src/pgraph/transformState.h +++ b/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 CompositionCache; + + typedef SimpleHashMap CompositionCache; CompositionCache _composition_cache; CompositionCache _invert_composition_cache; diff --git a/panda/src/putil/Sources.pp b/panda/src/putil/Sources.pp index d928b30724..cf0dade95c 100644 --- a/panda/src/putil/Sources.pp +++ b/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 \ diff --git a/panda/src/putil/putil_composite2.cxx b/panda/src/putil/putil_composite2.cxx index b9f935e82d..82deb04a46 100644 --- a/panda/src/putil/putil_composite2.cxx +++ b/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" diff --git a/panda/src/putil/simpleHashMap.I b/panda/src/putil/simpleHashMap.I new file mode 100644 index 0000000000..981304cda5 --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: SimpleHashMap::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +template +INLINE SimpleHashMap:: +SimpleHashMap(const Compare &comp) : + _table(NULL), + _deleted_chain(NULL), + _table_size(0), + _num_entries(0), + _comp(comp) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: SimpleHashMap::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +template +INLINE SimpleHashMap:: +~SimpleHashMap() { + clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: SimpleHashMap::swap +// Access: Public +// Description: Quickly exchanges the contents of this map and the +// other map. +//////////////////////////////////////////////////////////////////// +template +INLINE void SimpleHashMap:: +swap(SimpleHashMap &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 +int SimpleHashMap:: +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 +Value &SimpleHashMap:: +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 +INLINE bool SimpleHashMap:: +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 +void SimpleHashMap:: +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 +INLINE Value &SimpleHashMap:: +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 +INLINE int SimpleHashMap:: +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 +INLINE bool SimpleHashMap:: +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 +INLINE const Key &SimpleHashMap:: +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 +INLINE const Value &SimpleHashMap:: +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 +INLINE Value &SimpleHashMap:: +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 +INLINE void SimpleHashMap:: +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 +void SimpleHashMap:: +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 +INLINE int SimpleHashMap:: +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 +INLINE bool SimpleHashMap:: +is_empty() const { + return (_num_entries == 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: SimpleHashMap::output +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +template +void SimpleHashMap:: +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 +void SimpleHashMap:: +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 +bool SimpleHashMap:: +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 +INLINE size_t SimpleHashMap:: +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 +INLINE bool SimpleHashMap:: +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 +INLINE void SimpleHashMap:: +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 +INLINE void SimpleHashMap:: +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 +INLINE unsigned char *SimpleHashMap:: +get_exists_array() const { + return (unsigned char *)(_table + _table_size); +} + +//////////////////////////////////////////////////////////////////// +// Function: SimpleHashMap::new_table +// Access: Private +// Description: Allocates a brand new table. +//////////////////////////////////////////////////////////////////// +template +void SimpleHashMap:: +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 +INLINE bool SimpleHashMap:: +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 +void SimpleHashMap:: +expand_table() { + nassertv(_table_size != 0); + + SimpleHashMap 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); +} diff --git a/panda/src/putil/simpleHashMap.cxx b/panda/src/putil/simpleHashMap.cxx new file mode 100644 index 0000000000..653fdc8578 --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#include "simpleHashMap.h" diff --git a/panda/src/putil/simpleHashMap.h b/panda/src/putil/simpleHashMap.h new file mode 100644 index 0000000000..6f065f128a --- /dev/null +++ b/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 +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#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 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 +inline ostream &operator << (ostream &out, const SimpleHashMap &shm) { + shm.output(out); + return out; +} + +#ifndef CPPPARSER +#include "simpleHashMap.I" +#endif // CPPPARSER + +#endif +