/** * PANDA 3D SOFTWARE * Copyright (c) Carnegie Mellon University. All rights reserved. * * All use of this software is subject to the terms of the revised BSD * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * * @file simpleHashMap.I * @author drose * @date 2007-07-19 */ /** * */ template constexpr SimpleHashMap:: SimpleHashMap(const Compare &comp) : _table(nullptr), _deleted_chain(nullptr), _table_size(0), _num_entries(0), _comp(comp) { } /** * */ template INLINE SimpleHashMap:: SimpleHashMap(const SimpleHashMap ©) : _table_size(copy._table_size), _num_entries(copy._num_entries), _comp(copy._comp) { // We allocate enough bytes for _table_size elements of TableEntry, plus // _table_size * 4 more ints at the end (for the index array). size_t alloc_size = _table_size * (sizeof(TableEntry) + sizeof(int) * sparsity); _deleted_chain = memory_hook->get_deleted_chain(alloc_size); _table = (TableEntry *)_deleted_chain->allocate(alloc_size, TypeHandle::none()); for (size_t i = 0; i < _num_entries; ++i) { new(&_table[i]) TableEntry(copy._table[i]); } // Copy the index array. memcpy(get_index_array(), copy.get_index_array(), _table_size * sizeof(int) * sparsity); } /** * */ template INLINE SimpleHashMap:: SimpleHashMap(SimpleHashMap &&from) noexcept : _table(from._table), _deleted_chain(from._deleted_chain), _table_size(from._table_size), _num_entries(from._num_entries), _comp(std::move(from._comp)) { from._table = nullptr; from._deleted_chain = nullptr; from._table_size = 0; from._num_entries = 0; } /** * */ template INLINE SimpleHashMap:: ~SimpleHashMap() { clear(); } /** * */ template INLINE SimpleHashMap &SimpleHashMap:: operator = (const SimpleHashMap ©) { if (this != ©) { _table_size = copy._table_size; _num_entries = copy._num_entries; _comp = copy._comp; // We allocate enough bytes for _table_size elements of TableEntry, plus // _table_size * 4 more ints at the end (for the index array). size_t alloc_size = _table_size * (sizeof(TableEntry) + sizeof(int) * sparsity); _deleted_chain = memory_hook->get_deleted_chain(alloc_size); _table = (TableEntry *)_deleted_chain->allocate(alloc_size, TypeHandle::none()); for (size_t i = 0; i < _num_entries; ++i) { new(&_table[i]) TableEntry(copy._table[i]); } // Copy the index array. memcpy(get_index_array(), copy.get_index_array(), _table_size * sizeof(int) * sparsity); } return *this; } /** * */ template INLINE SimpleHashMap &SimpleHashMap:: operator = (SimpleHashMap &&from) noexcept { if (this != &from) { _table = from._table; _deleted_chain = from._deleted_chain; _table_size = from._table_size; _num_entries = from._num_entries; _comp = std::move(from._comp); from._table = nullptr; from._deleted_chain = nullptr; from._table_size = 0; from._num_entries = 0; } } /** * 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; } /** * 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; } int slot = find_slot(key); if (slot >= 0) { return get_index_array()[slot]; } else { // The key is not in the table. return -1; } } /** * Records the indicated key/data pair in the map. If the key was already * present, silently replaces it. Returns the index at which it was stored. */ template int 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, -1); new_table(); int pos = store_new_element(get_hash(key), key, data); #ifdef _DEBUG nassertr(validate(), pos); #endif return pos; } consider_expand_table(); const int *index_array = get_index_array(); size_t hash = get_hash(key); int index = index_array[hash]; if (index < 0) { // This element is not already in the map; add it. if (consider_expand_table()) { return store(key, data); } index = store_new_element(hash, key, data); #ifdef _DEBUG nassertr(validate(), index); #endif return index; } if (is_element(index, key)) { // This element is already in the map; replace the data at that key. set_data(index, data); #ifdef _DEBUG nassertr(validate(), index); #endif return index; } // There was some other key at the hashed slot. That's a hash conflict. // Record this entry at a later position. size_t slot = next_hash(hash); while (slot != hash) { index = index_array[slot]; if (index < 0) { if (consider_expand_table()) { return store(key, data); } index = store_new_element(slot, key, data); #ifdef _DEBUG nassertr(validate(), index); #endif return index; } if (is_element(index, key)) { set_data(index, data); #ifdef _DEBUG nassertr(validate(), index); #endif return index; } slot = next_hash(slot); } // Shouldn't get here unless _num_entries == _table_size, which shouldn't be // possible due to consider_expand_table(). nassertr(false, -1); return -1; // To satisfy compiler } /** * Removes the indicated key and its associated data from the table. Returns * true if the key was removed, false if it was not present. * * Iterator safety: To perform removal during iteration, revisit the element * at the current index if removal succeeds, keeping in mind that the number * of elements has now shrunk by one. */ template INLINE bool SimpleHashMap:: remove(const Key &key) { if (_num_entries == 0) { // Special case: the table is empty. return false; } int *index_array = get_index_array(); size_t slot = (size_t)find_slot(key); if (slot == (size_t)-1) { // It wasn't in the hash map. return false; } // Now remove this element. size_t last = _num_entries - 1; size_t index = (size_t)index_array[slot]; if (index < _num_entries) { // Find the last element in the index array. int other_slot = find_slot(_table[last]._key); nassertr(other_slot != -1, false); nassertr(index_array[(size_t)other_slot] == (int)last, false); // Swap it with the last one, so that we don't get any gaps in the table // of entries. _table[index] = std::move(_table[last]); index_array[(size_t)other_slot] = index; } _table[last].~TableEntry(); _num_entries = last; // It's important that we do this after the second find_slot, above, since // it might otherwise fail due to the unexpected gap, since some indices may // not be at their ideal positions right now. index_array[slot] = -1; //if (consider_shrink_table()) { // // No need to worry about that gap; resize_table() will rebuild the index. // return true; //} // Now we have put a hole in the index array. If there was a hash conflict // in the slot after this one, we have to move it down to close the hole. slot = next_hash(slot); while (has_slot(slot)) { size_t index = (size_t)index_array[slot]; size_t wants_slot = get_hash(_table[index]._key); if (wants_slot != slot) { // 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_slot != slot && has_slot(wants_slot)) { wants_slot = next_hash(wants_slot); } if (wants_slot != slot) { // We just have to flip the slots in the index array; we can keep the // elements in the table where they are. index_array[wants_slot] = index; index_array[slot] = -1; } } // 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. slot = next_hash(slot); } #ifdef _DEBUG nassertr(validate(), true); #endif return true; } /** * Completely empties the table. */ template void SimpleHashMap:: clear() { if (_table_size != 0) { for (size_t i = 0; i < _num_entries; ++i) { _table[i].~TableEntry(); } _deleted_chain->deallocate(_table, TypeHandle::none()); _table = nullptr; _deleted_chain = nullptr; _table_size = 0; _num_entries = 0; } } /** * 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) { index = store(key, Value()); } return modify_data(index); } /** * Returns the total number of entries in the table. Same as get_num_entries. */ template constexpr size_t SimpleHashMap:: size() const { return _num_entries; } /** * Returns the key in the nth entry of the table. * * @param n should be in the range 0 <= n < size(). */ template INLINE const Key &SimpleHashMap:: get_key(size_t n) const { nassertr(n < _num_entries, _table[n]._key); return _table[n]._key; } /** * Returns the data in the nth entry of the table. * * @param n should be in the range 0 <= n < size(). */ template INLINE const Value &SimpleHashMap:: get_data(size_t n) const { nassertr(n < _num_entries, _table[n].get_data()); return _table[n].get_data(); } /** * Returns a modifiable reference to the data in the nth entry of the table. * * @param n should be in the range 0 <= n < size(). */ template INLINE Value &SimpleHashMap:: modify_data(size_t n) { nassertr(n < _num_entries, _table[n].modify_data()); return _table[n].modify_data(); } /** * Changes the data for the nth entry of the table. * * @param n should be in the range 0 <= n < size(). */ template INLINE void SimpleHashMap:: set_data(size_t n, const Value &data) { nassertv(n < _num_entries); _table[n].set_data(data); } /** * Changes the data for the nth entry of the table. * * @param n should be in the range 0 <= n < size(). */ template INLINE void SimpleHashMap:: set_data(size_t n, Value &&data) { nassertv(n < _num_entries); _table[n].set_data(std::move(data)); } /** * Removes the nth entry from the table. * * @param n should be in the range 0 <= n < size(). */ template void SimpleHashMap:: remove_element(size_t n) { nassertv(n < _num_entries); remove(_table[n]._key); } /** * Returns the number of active entries in the table. Same as size(). */ template INLINE size_t SimpleHashMap:: get_num_entries() const { return _num_entries; } /** * Returns true if the table is empty; i.e. get_num_entries() == 0. */ template INLINE bool SimpleHashMap:: is_empty() const { return (_num_entries == 0); } /** * */ template void SimpleHashMap:: output(std::ostream &out) const { out << "SimpleHashMap (" << _num_entries << " entries): ["; const int *index_array = get_index_array(); size_t num_slots = _table_size * sparsity; for (size_t slot = 0; slot < num_slots; ++slot) { if (!has_slot(slot)) { out << " *"; } else { size_t index = (size_t)index_array[slot]; out << " " << index; size_t ideal_slot = get_hash(_table[index]._key); if (ideal_slot != slot) { // This was misplaced as the result of a hash conflict. Report how // far off it is. out << "(" << ((_table_size + slot - ideal_slot) & (num_slots - 1)) << ")"; } } } out << " ]"; } /** * */ template void SimpleHashMap:: write(std::ostream &out) const { output(out); out << "\n"; for (size_t i = 0; i < _num_entries; ++i) { out << " " << _table[i]._key << " (hash " << get_hash(_table[i]._key) << ")\n"; } } /** * 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; const int *index_array = get_index_array(); size_t num_slots = _table_size * sparsity; for (size_t slot = 0; slot < num_slots; ++slot) { if (has_slot(slot)) { size_t index = (size_t)index_array[slot]; ++count; if (index >= _num_entries) { util_cat.error() << "SimpleHashMap " << this << " is invalid: slot " << slot << " contains index " << index << " which is past the end of the" " table\n"; write(util_cat.error(false)); return false; } nassertd(index < _num_entries) continue; size_t ideal_slot = get_hash(_table[index]._key); size_t wants_slot = ideal_slot; while (wants_slot != slot && has_slot(wants_slot)) { wants_slot = next_hash(wants_slot); } if (wants_slot != slot) { util_cat.error() << "SimpleHashMap " << this << " is invalid: key " << _table[index]._key << " should be in slot " << wants_slot << " instead of " << slot << " (ideal is " << ideal_slot << ")\n"; write(util_cat.error(false)); return false; } } } if (count != _num_entries) { util_cat.error() << "SimpleHashMap " << this << " is invalid: reports " << _num_entries << " entries, actually has " << count << "\n"; write(util_cat.error(false)); return false; } return true; } /** * 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 * sparsity) - 1); } /** * Given a hash value, increments it, looping around the hash space. */ template INLINE size_t SimpleHashMap:: next_hash(size_t hash) const { return (hash + 1) & ((_table_size * sparsity) - 1); } /** * Finds the slot in which the given key should fit. */ template INLINE int SimpleHashMap:: find_slot(const Key &key) const { const int *index_array = get_index_array(); size_t hash = get_hash(key); int index = index_array[hash]; if (index < 0) { return -1; } if (is_element((size_t)index, key)) { return hash; } // 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 slot = next_hash(hash); while (slot != hash && has_slot(slot)) { if (is_element((size_t)index_array[slot], key)) { return (int)slot; } slot = next_hash(slot); } return -1; } /** * Returns true if the given slot refers to an element. */ template INLINE bool SimpleHashMap:: has_slot(size_t slot) const { return get_index_array()[slot] >= 0; } /** * Returns true if element n matches key. */ template INLINE bool SimpleHashMap:: is_element(size_t n, const Key &key) const { nassertr(n < _num_entries, false); return _comp.is_equal(_table[n]._key, key); } /** * Constructs a new TableEntry with the given slot, storing the indicated key * and value. */ template INLINE size_t SimpleHashMap:: store_new_element(size_t slot, const Key &key, const Value &data) { size_t index = _num_entries++; new(&_table[index]) TableEntry(key, data); nassertr(get_index_array()[slot] == -1, index) get_index_array()[slot] = index; return index; } /** * Returns the beginning of the array of _table_size ints that are the indices * pointing to the location within the table where the elements are stored. * within the table. */ template INLINE int *SimpleHashMap:: get_index_array() const { return (int *)(_table + _table_size); } /** * 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 really small. Maybe // that's the right answer. _table_size = 2; // We allocate enough bytes for _table_size elements of TableEntry, plus // _table_size * 4 more ints at the end (for the index array). size_t alloc_size = _table_size * (sizeof(TableEntry) + sizeof(int) * sparsity); _deleted_chain = memory_hook->get_deleted_chain(alloc_size); _table = (TableEntry *)_deleted_chain->allocate(alloc_size, TypeHandle::none()); memset(get_index_array(), -1, _table_size * sizeof(int) * sparsity); } /** * 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) { return false; } else { resize_table(_table_size << 1); return true; } } /** * Shrinks the table if the allocated storage is significantly larger than the * number of elements in it. Returns true if shrunk, false otherwise. */ template INLINE bool SimpleHashMap:: consider_shrink_table() { // If the number of elements gets less than an eighth of the table size, we // know it's probably time to shrink it down. if (_table_size <= 16 || _num_entries >= (_table_size >> 3)) { return false; } else { size_t new_size = _table_size; do { new_size >>= 1; } while (new_size >= 16 && _num_entries < (new_size >> 2)); resize_table(new_size); return true; } } /** * Resizes the existing table. */ template void SimpleHashMap:: resize_table(size_t new_size) { nassertv(_table_size != 0); nassertv(new_size >= _num_entries); DeletedBufferChain *old_chain = _deleted_chain; TableEntry *old_table = _table; _table_size = new_size; // We allocate enough bytes for _table_size elements of TableEntry, plus // _table_size * sparsity more ints at the end (for the sparse index array). size_t alloc_size = _table_size * sizeof(TableEntry) + _table_size * sparsity * sizeof(int); _deleted_chain = memory_hook->get_deleted_chain(alloc_size); _table = (TableEntry *)_deleted_chain->allocate(alloc_size, TypeHandle::none()); int *index_array = get_index_array(); memset(index_array, -1, _table_size * sizeof(int) * sparsity); // Now copy the entries from the old table into the new table. We don't // have to reorder these, fortunately. Hopefully, a smart compiler will // optimize this to a memcpy. for (size_t i = 0; i < _num_entries; ++i) { new(&_table[i]) TableEntry(std::move(old_table[i])); old_table[i].~TableEntry(); } // We don't need this old thing anymore. old_chain->deallocate(old_table, TypeHandle::none()); // Reindex the table. for (size_t i = 0; i < _num_entries; ++i) { size_t slot = get_hash(_table[i]._key); while (has_slot(slot)) { // Hash conflict; look for a better spot. This has to succeed. slot = next_hash(slot); } index_array[slot] = (int)i; } nassertv(validate()); }