|
@@ -34,524 +34,558 @@
|
|
#include <godot_cpp/core/error_macros.hpp>
|
|
#include <godot_cpp/core/error_macros.hpp>
|
|
#include <godot_cpp/core/memory.hpp>
|
|
#include <godot_cpp/core/memory.hpp>
|
|
#include <godot_cpp/templates/hashfuncs.hpp>
|
|
#include <godot_cpp/templates/hashfuncs.hpp>
|
|
-#include <godot_cpp/templates/list.hpp>
|
|
|
|
|
|
+#include <godot_cpp/templates/pair.hpp>
|
|
|
|
+
|
|
|
|
+namespace godot {
|
|
|
|
|
|
/**
|
|
/**
|
|
- * @class HashMap
|
|
|
|
|
|
+ * A HashMap implementation that uses open addressing with Robin Hood hashing.
|
|
|
|
+ * Robin Hood hashing swaps out entries that have a smaller probing distance
|
|
|
|
+ * than the to-be-inserted entry, that evens out the average probing distance
|
|
|
|
+ * and enables faster lookups. Backward shift deletion is employed to further
|
|
|
|
+ * improve the performance and to avoid infinite loops in rare cases.
|
|
*
|
|
*
|
|
- * Implementation of a standard Hashing HashMap, for quick lookups of Data associated with a Key.
|
|
|
|
- * The implementation provides hashers for the default types, if you need a special kind of hasher, provide
|
|
|
|
- * your own.
|
|
|
|
- * @param TKey Key, search is based on it, needs to be hasheable. It is unique in this container.
|
|
|
|
- * @param TData Data, data associated with the key
|
|
|
|
- * @param Hasher Hasher object, needs to provide a valid static hash function for TKey
|
|
|
|
- * @param Comparator comparator object, needs to be able to safely compare two TKey values.
|
|
|
|
- * It needs to ensure that x == x for any items inserted in the map. Bear in mind that nan != nan when implementing an equality check.
|
|
|
|
- * @param MIN_HASH_TABLE_POWER Miminum size of the hash table, as a power of two. You rarely need to change this parameter.
|
|
|
|
- * @param RELATIONSHIP Relationship at which the hash table is resized. if amount of elements is RELATIONSHIP
|
|
|
|
- * times bigger than the hash table, table is resized to solve this condition. if RELATIONSHIP is zero, table is always MIN_HASH_TABLE_POWER.
|
|
|
|
|
|
+ * Keys and values are stored in a double linked list by insertion order. This
|
|
|
|
+ * has a slight performance overhead on lookup, which can be mostly compensated
|
|
|
|
+ * using a paged allocator if required.
|
|
*
|
|
*
|
|
|
|
+ * The assignment operator copy the pairs from one map to the other.
|
|
*/
|
|
*/
|
|
|
|
|
|
-namespace godot {
|
|
|
|
|
|
+template <class TKey, class TValue>
|
|
|
|
+struct HashMapElement {
|
|
|
|
+ HashMapElement *next = nullptr;
|
|
|
|
+ HashMapElement *prev = nullptr;
|
|
|
|
+ KeyValue<TKey, TValue> data;
|
|
|
|
+ HashMapElement() {}
|
|
|
|
+ HashMapElement(const TKey &p_key, const TValue &p_value) :
|
|
|
|
+ data(p_key, p_value) {}
|
|
|
|
+};
|
|
|
|
|
|
-template <class TKey, class TData, class Hasher = HashMapHasherDefault, class Comparator = HashMapComparatorDefault<TKey>, uint8_t MIN_HASH_TABLE_POWER = 3, uint8_t RELATIONSHIP = 8>
|
|
|
|
|
|
+template <class TKey, class TValue,
|
|
|
|
+ class Hasher = HashMapHasherDefault,
|
|
|
|
+ class Comparator = HashMapComparatorDefault<TKey>,
|
|
|
|
+ class Allocator = DefaultTypedAllocator<HashMapElement<TKey, TValue>>>
|
|
class HashMap {
|
|
class HashMap {
|
|
public:
|
|
public:
|
|
- struct Pair {
|
|
|
|
- TKey key;
|
|
|
|
- TData data;
|
|
|
|
|
|
+ const uint32_t MIN_CAPACITY_INDEX = 2; // Use a prime.
|
|
|
|
+ const float MAX_OCCUPANCY = 0.75;
|
|
|
|
+ const uint32_t EMPTY_HASH = 0;
|
|
|
|
|
|
- Pair(const TKey &p_key) :
|
|
|
|
- key(p_key),
|
|
|
|
- data() {}
|
|
|
|
- Pair(const TKey &p_key, const TData &p_data) :
|
|
|
|
- key(p_key),
|
|
|
|
- data(p_data) {
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
|
|
+private:
|
|
|
|
+ Allocator element_alloc;
|
|
|
|
+ HashMapElement<TKey, TValue> **elements = nullptr;
|
|
|
|
+ uint32_t *hashes = nullptr;
|
|
|
|
+ HashMapElement<TKey, TValue> *head_element = nullptr;
|
|
|
|
+ HashMapElement<TKey, TValue> *tail_element = nullptr;
|
|
|
|
|
|
- struct Element {
|
|
|
|
- private:
|
|
|
|
- friend class HashMap;
|
|
|
|
|
|
+ uint32_t capacity_index = 0;
|
|
|
|
+ uint32_t num_elements = 0;
|
|
|
|
|
|
- uint32_t hash = 0;
|
|
|
|
- Element *next = nullptr;
|
|
|
|
- Element() {}
|
|
|
|
- Pair pair;
|
|
|
|
|
|
+ _FORCE_INLINE_ uint32_t _hash(const TKey &p_key) const {
|
|
|
|
+ uint32_t hash = Hasher::hash(p_key);
|
|
|
|
|
|
- public:
|
|
|
|
- const TKey &key() const {
|
|
|
|
- return pair.key;
|
|
|
|
|
|
+ if (unlikely(hash == EMPTY_HASH)) {
|
|
|
|
+ hash = EMPTY_HASH + 1;
|
|
}
|
|
}
|
|
|
|
|
|
- TData &value() {
|
|
|
|
- return pair.data;
|
|
|
|
- }
|
|
|
|
|
|
+ return hash;
|
|
|
|
+ }
|
|
|
|
|
|
- const TData &value() const {
|
|
|
|
- return pair.value();
|
|
|
|
|
|
+ _FORCE_INLINE_ uint32_t _get_probe_length(uint32_t p_pos, uint32_t p_hash, uint32_t p_capacity) const {
|
|
|
|
+ uint32_t original_pos = p_hash % p_capacity;
|
|
|
|
+ return (p_pos - original_pos + p_capacity) % p_capacity;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ bool _lookup_pos(const TKey &p_key, uint32_t &r_pos) const {
|
|
|
|
+ if (elements == nullptr) {
|
|
|
|
+ return false; // Failed lookups, no elements
|
|
}
|
|
}
|
|
|
|
|
|
- Element(const TKey &p_key) :
|
|
|
|
- pair(p_key) {}
|
|
|
|
- Element(const Element &p_other) :
|
|
|
|
- hash(p_other.hash),
|
|
|
|
- pair(p_other.pair.key, p_other.pair.data) {}
|
|
|
|
- };
|
|
|
|
|
|
+ uint32_t capacity = hash_table_size_primes[capacity_index];
|
|
|
|
+ uint32_t hash = _hash(p_key);
|
|
|
|
+ uint32_t pos = hash % capacity;
|
|
|
|
+ uint32_t distance = 0;
|
|
|
|
|
|
-private:
|
|
|
|
- Element **hash_table = nullptr;
|
|
|
|
- uint8_t hash_table_power = 0;
|
|
|
|
- uint32_t elements = 0;
|
|
|
|
|
|
+ while (true) {
|
|
|
|
+ if (hashes[pos] == EMPTY_HASH) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
|
|
- void make_hash_table() {
|
|
|
|
- ERR_FAIL_COND(hash_table);
|
|
|
|
|
|
+ if (distance > _get_probe_length(pos, hashes[pos], capacity)) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
|
|
- hash_table = memnew_arr(Element *, (1 << MIN_HASH_TABLE_POWER));
|
|
|
|
|
|
+ if (hashes[pos] == hash && Comparator::compare(elements[pos]->data.key, p_key)) {
|
|
|
|
+ r_pos = pos;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
|
|
- hash_table_power = MIN_HASH_TABLE_POWER;
|
|
|
|
- elements = 0;
|
|
|
|
- for (int i = 0; i < (1 << MIN_HASH_TABLE_POWER); i++) {
|
|
|
|
- hash_table[i] = nullptr;
|
|
|
|
|
|
+ pos = (pos + 1) % capacity;
|
|
|
|
+ distance++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- void erase_hash_table() {
|
|
|
|
- ERR_FAIL_COND_MSG(elements, "Cannot erase hash table if there are still elements inside.");
|
|
|
|
-
|
|
|
|
- memdelete_arr(hash_table);
|
|
|
|
- hash_table = nullptr;
|
|
|
|
- hash_table_power = 0;
|
|
|
|
- elements = 0;
|
|
|
|
- }
|
|
|
|
|
|
+ void _insert_with_hash(uint32_t p_hash, HashMapElement<TKey, TValue> *p_value) {
|
|
|
|
+ uint32_t capacity = hash_table_size_primes[capacity_index];
|
|
|
|
+ uint32_t hash = p_hash;
|
|
|
|
+ HashMapElement<TKey, TValue> *value = p_value;
|
|
|
|
+ uint32_t distance = 0;
|
|
|
|
+ uint32_t pos = hash % capacity;
|
|
|
|
|
|
- void check_hash_table() {
|
|
|
|
- int new_hash_table_power = -1;
|
|
|
|
|
|
+ while (true) {
|
|
|
|
+ if (hashes[pos] == EMPTY_HASH) {
|
|
|
|
+ elements[pos] = value;
|
|
|
|
+ hashes[pos] = hash;
|
|
|
|
|
|
- if ((int)elements > ((1 << hash_table_power) * RELATIONSHIP)) {
|
|
|
|
- /* rehash up */
|
|
|
|
- new_hash_table_power = hash_table_power + 1;
|
|
|
|
|
|
+ num_elements++;
|
|
|
|
|
|
- while ((int)elements > ((1 << new_hash_table_power) * RELATIONSHIP)) {
|
|
|
|
- new_hash_table_power++;
|
|
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
|
|
|
|
- } else if ((hash_table_power > (int)MIN_HASH_TABLE_POWER) && ((int)elements < ((1 << (hash_table_power - 1)) * RELATIONSHIP))) {
|
|
|
|
- /* rehash down */
|
|
|
|
- new_hash_table_power = hash_table_power - 1;
|
|
|
|
-
|
|
|
|
- while ((int)elements < ((1 << (new_hash_table_power - 1)) * RELATIONSHIP)) {
|
|
|
|
- new_hash_table_power--;
|
|
|
|
|
|
+ // Not an empty slot, let's check the probing length of the existing one.
|
|
|
|
+ uint32_t existing_probe_len = _get_probe_length(pos, hashes[pos], capacity);
|
|
|
|
+ if (existing_probe_len < distance) {
|
|
|
|
+ SWAP(hash, hashes[pos]);
|
|
|
|
+ SWAP(value, elements[pos]);
|
|
|
|
+ distance = existing_probe_len;
|
|
}
|
|
}
|
|
|
|
|
|
- if (new_hash_table_power < (int)MIN_HASH_TABLE_POWER) {
|
|
|
|
- new_hash_table_power = MIN_HASH_TABLE_POWER;
|
|
|
|
- }
|
|
|
|
|
|
+ pos = (pos + 1) % capacity;
|
|
|
|
+ distance++;
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- if (new_hash_table_power == -1) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+ void _resize_and_rehash(uint32_t p_new_capacity_index) {
|
|
|
|
+ uint32_t old_capacity = hash_table_size_primes[capacity_index];
|
|
|
|
|
|
- Element **new_hash_table = memnew_arr(Element *, ((uint64_t)1 << new_hash_table_power));
|
|
|
|
- ERR_FAIL_COND_MSG(!new_hash_table, "Out of memory.");
|
|
|
|
|
|
+ // Capacity can't be 0.
|
|
|
|
+ capacity_index = MAX((uint32_t)MIN_CAPACITY_INDEX, p_new_capacity_index);
|
|
|
|
|
|
- for (int i = 0; i < (1 << new_hash_table_power); i++) {
|
|
|
|
- new_hash_table[i] = nullptr;
|
|
|
|
- }
|
|
|
|
|
|
+ uint32_t capacity = hash_table_size_primes[capacity_index];
|
|
|
|
|
|
- if (hash_table) {
|
|
|
|
- for (int i = 0; i < (1 << hash_table_power); i++) {
|
|
|
|
- while (hash_table[i]) {
|
|
|
|
- Element *se = hash_table[i];
|
|
|
|
- hash_table[i] = se->next;
|
|
|
|
- int new_pos = se->hash & ((1 << new_hash_table_power) - 1);
|
|
|
|
- se->next = new_hash_table[new_pos];
|
|
|
|
- new_hash_table[new_pos] = se;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ HashMapElement<TKey, TValue> **old_elements = elements;
|
|
|
|
+ uint32_t *old_hashes = hashes;
|
|
|
|
|
|
- memdelete_arr(hash_table);
|
|
|
|
- }
|
|
|
|
- hash_table = new_hash_table;
|
|
|
|
- hash_table_power = new_hash_table_power;
|
|
|
|
- }
|
|
|
|
|
|
+ num_elements = 0;
|
|
|
|
+ hashes = reinterpret_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
|
|
|
|
+ elements = reinterpret_cast<HashMapElement<TKey, TValue> **>(Memory::alloc_static(sizeof(HashMapElement<TKey, TValue> *) * capacity));
|
|
|
|
|
|
- /* I want to have only one function.. */
|
|
|
|
- _FORCE_INLINE_ const Element *get_element(const TKey &p_key) const {
|
|
|
|
- uint32_t hash = Hasher::hash(p_key);
|
|
|
|
- uint32_t index = hash & ((1 << hash_table_power) - 1);
|
|
|
|
|
|
+ for (uint32_t i = 0; i < capacity; i++) {
|
|
|
|
+ hashes[i] = 0;
|
|
|
|
+ elements[i] = nullptr;
|
|
|
|
+ }
|
|
|
|
|
|
- Element *e = hash_table[index];
|
|
|
|
|
|
+ if (old_capacity == 0) {
|
|
|
|
+ // Nothing to do.
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
|
|
- while (e) {
|
|
|
|
- /* checking hash first avoids comparing key, which may take longer */
|
|
|
|
- if (e->hash == hash && Comparator::compare(e->pair.key, p_key)) {
|
|
|
|
- /* the pair exists in this hashtable, so just update data */
|
|
|
|
- return e;
|
|
|
|
|
|
+ for (uint32_t i = 0; i < old_capacity; i++) {
|
|
|
|
+ if (old_hashes[i] == EMPTY_HASH) {
|
|
|
|
+ continue;
|
|
}
|
|
}
|
|
|
|
|
|
- e = e->next;
|
|
|
|
|
|
+ _insert_with_hash(old_hashes[i], old_elements[i]);
|
|
}
|
|
}
|
|
|
|
|
|
- return nullptr;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- Element *create_element(const TKey &p_key) {
|
|
|
|
- /* if element doesn't exist, create it */
|
|
|
|
- Element *e = memnew(Element(p_key));
|
|
|
|
- ERR_FAIL_COND_V_MSG(!e, nullptr, "Out of memory.");
|
|
|
|
- uint32_t hash = Hasher::hash(p_key);
|
|
|
|
- uint32_t index = hash & ((1 << hash_table_power) - 1);
|
|
|
|
- e->next = hash_table[index];
|
|
|
|
- e->hash = hash;
|
|
|
|
-
|
|
|
|
- hash_table[index] = e;
|
|
|
|
- elements++;
|
|
|
|
-
|
|
|
|
- return e;
|
|
|
|
|
|
+ Memory::free_static(old_elements);
|
|
|
|
+ Memory::free_static(old_hashes);
|
|
}
|
|
}
|
|
|
|
|
|
- void copy_from(const HashMap &p_t) {
|
|
|
|
- if (&p_t == this) {
|
|
|
|
- return; /* much less bother with that */
|
|
|
|
- }
|
|
|
|
|
|
+ _FORCE_INLINE_ HashMapElement<TKey, TValue> *_insert(const TKey &p_key, const TValue &p_value, bool p_front_insert = false) {
|
|
|
|
+ uint32_t capacity = hash_table_size_primes[capacity_index];
|
|
|
|
+ if (unlikely(elements == nullptr)) {
|
|
|
|
+ // Allocate on demand to save memory.
|
|
|
|
|
|
- clear();
|
|
|
|
|
|
+ hashes = reinterpret_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
|
|
|
|
+ elements = reinterpret_cast<HashMapElement<TKey, TValue> **>(Memory::alloc_static(sizeof(HashMapElement<TKey, TValue> *) * capacity));
|
|
|
|
|
|
- if (!p_t.hash_table || p_t.hash_table_power == 0) {
|
|
|
|
- return; /* not copying from empty table */
|
|
|
|
|
|
+ for (uint32_t i = 0; i < capacity; i++) {
|
|
|
|
+ hashes[i] = EMPTY_HASH;
|
|
|
|
+ elements[i] = nullptr;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- hash_table = memnew_arr(Element *, (uint64_t)1 << p_t.hash_table_power);
|
|
|
|
- hash_table_power = p_t.hash_table_power;
|
|
|
|
- elements = p_t.elements;
|
|
|
|
-
|
|
|
|
- for (int i = 0; i < (1 << p_t.hash_table_power); i++) {
|
|
|
|
- hash_table[i] = nullptr;
|
|
|
|
-
|
|
|
|
- const Element *e = p_t.hash_table[i];
|
|
|
|
|
|
+ uint32_t pos = 0;
|
|
|
|
+ bool exists = _lookup_pos(p_key, pos);
|
|
|
|
|
|
- while (e) {
|
|
|
|
- Element *le = memnew(Element(*e)); /* local element */
|
|
|
|
|
|
+ if (exists) {
|
|
|
|
+ elements[pos]->data.value = p_value;
|
|
|
|
+ return elements[pos];
|
|
|
|
+ } else {
|
|
|
|
+ if (num_elements + 1 > MAX_OCCUPANCY * capacity) {
|
|
|
|
+ ERR_FAIL_COND_V_MSG(capacity_index + 1 == HASH_TABLE_SIZE_MAX, nullptr, "Hash table maximum capacity reached, aborting insertion.");
|
|
|
|
+ _resize_and_rehash(capacity_index + 1);
|
|
|
|
+ }
|
|
|
|
|
|
- /* add to list and reassign pointers */
|
|
|
|
- le->next = hash_table[i];
|
|
|
|
- hash_table[i] = le;
|
|
|
|
|
|
+ HashMapElement<TKey, TValue> *elem = element_alloc.new_allocation(HashMapElement<TKey, TValue>(p_key, p_value));
|
|
|
|
|
|
- e = e->next;
|
|
|
|
|
|
+ if (tail_element == nullptr) {
|
|
|
|
+ head_element = elem;
|
|
|
|
+ tail_element = elem;
|
|
|
|
+ } else if (p_front_insert) {
|
|
|
|
+ head_element->prev = elem;
|
|
|
|
+ elem->next = head_element;
|
|
|
|
+ head_element = elem;
|
|
|
|
+ } else {
|
|
|
|
+ tail_element->next = elem;
|
|
|
|
+ elem->prev = tail_element;
|
|
|
|
+ tail_element = elem;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ uint32_t hash = _hash(p_key);
|
|
|
|
+ _insert_with_hash(hash, elem);
|
|
|
|
+ return elem;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public:
|
|
public:
|
|
- Element *set(const TKey &p_key, const TData &p_data) {
|
|
|
|
- return set(Pair(p_key, p_data));
|
|
|
|
- }
|
|
|
|
|
|
+ _FORCE_INLINE_ uint32_t get_capacity() const { return hash_table_size_primes[capacity_index]; }
|
|
|
|
+ _FORCE_INLINE_ uint32_t size() const { return num_elements; }
|
|
|
|
|
|
- Element *set(const Pair &p_pair) {
|
|
|
|
- Element *e = nullptr;
|
|
|
|
- if (!hash_table) {
|
|
|
|
- make_hash_table(); // if no table, make one
|
|
|
|
- } else {
|
|
|
|
- e = const_cast<Element *>(get_element(p_pair.key));
|
|
|
|
- }
|
|
|
|
|
|
+ /* Standard Godot Container API */
|
|
|
|
|
|
- /* if we made it up to here, the pair doesn't exist, create and assign */
|
|
|
|
|
|
+ bool is_empty() const {
|
|
|
|
+ return num_elements == 0;
|
|
|
|
+ }
|
|
|
|
|
|
- if (!e) {
|
|
|
|
- e = create_element(p_pair.key);
|
|
|
|
- if (!e) {
|
|
|
|
- return nullptr;
|
|
|
|
- }
|
|
|
|
- check_hash_table(); // perform mantenience routine
|
|
|
|
|
|
+ void clear() {
|
|
|
|
+ if (elements == nullptr) {
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
|
|
+ uint32_t capacity = hash_table_size_primes[capacity_index];
|
|
|
|
+ for (uint32_t i = 0; i < capacity; i++) {
|
|
|
|
+ if (hashes[i] == EMPTY_HASH) {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
|
|
- e->pair.data = p_pair.data;
|
|
|
|
- return e;
|
|
|
|
- }
|
|
|
|
|
|
+ hashes[i] = EMPTY_HASH;
|
|
|
|
+ element_alloc.delete_allocation(elements[i]);
|
|
|
|
+ elements[i] = nullptr;
|
|
|
|
+ }
|
|
|
|
|
|
- bool has(const TKey &p_key) const {
|
|
|
|
- return getptr(p_key) != nullptr;
|
|
|
|
|
|
+ tail_element = nullptr;
|
|
|
|
+ head_element = nullptr;
|
|
|
|
+ num_elements = 0;
|
|
}
|
|
}
|
|
|
|
|
|
- /**
|
|
|
|
- * Get a key from data, return a const reference.
|
|
|
|
- * WARNING: this doesn't check errors, use either getptr and check nullptr, or check
|
|
|
|
- * first with has(key)
|
|
|
|
- */
|
|
|
|
-
|
|
|
|
- const TData &get(const TKey &p_key) const {
|
|
|
|
- const TData *res = getptr(p_key);
|
|
|
|
- CRASH_COND_MSG(!res, "Map key not found.");
|
|
|
|
- return *res;
|
|
|
|
|
|
+ TValue &get(const TKey &p_key) {
|
|
|
|
+ uint32_t pos = 0;
|
|
|
|
+ bool exists = _lookup_pos(p_key, pos);
|
|
|
|
+ CRASH_COND_MSG(!exists, "HashMap key not found.");
|
|
|
|
+ return elements[pos]->data.value;
|
|
}
|
|
}
|
|
|
|
|
|
- TData &get(const TKey &p_key) {
|
|
|
|
- TData *res = getptr(p_key);
|
|
|
|
- CRASH_COND_MSG(!res, "Map key not found.");
|
|
|
|
- return *res;
|
|
|
|
|
|
+ const TValue &get(const TKey &p_key) const {
|
|
|
|
+ uint32_t pos = 0;
|
|
|
|
+ bool exists = _lookup_pos(p_key, pos);
|
|
|
|
+ CRASH_COND_MSG(!exists, "HashMap key not found.");
|
|
|
|
+ return elements[pos]->data.value;
|
|
}
|
|
}
|
|
|
|
|
|
- /**
|
|
|
|
- * Same as get, except it can return nullptr when item was not found.
|
|
|
|
- * This is mainly used for speed purposes.
|
|
|
|
- */
|
|
|
|
|
|
+ const TValue *getptr(const TKey &p_key) const {
|
|
|
|
+ uint32_t pos = 0;
|
|
|
|
+ bool exists = _lookup_pos(p_key, pos);
|
|
|
|
|
|
- _FORCE_INLINE_ TData *getptr(const TKey &p_key) {
|
|
|
|
- if (unlikely(!hash_table)) {
|
|
|
|
- return nullptr;
|
|
|
|
|
|
+ if (exists) {
|
|
|
|
+ return &elements[pos]->data.value;
|
|
}
|
|
}
|
|
|
|
+ return nullptr;
|
|
|
|
+ }
|
|
|
|
|
|
- Element *e = const_cast<Element *>(get_element(p_key));
|
|
|
|
|
|
+ TValue *getptr(const TKey &p_key) {
|
|
|
|
+ uint32_t pos = 0;
|
|
|
|
+ bool exists = _lookup_pos(p_key, pos);
|
|
|
|
|
|
- if (e) {
|
|
|
|
- return &e->pair.data;
|
|
|
|
|
|
+ if (exists) {
|
|
|
|
+ return &elements[pos]->data.value;
|
|
}
|
|
}
|
|
-
|
|
|
|
return nullptr;
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
- _FORCE_INLINE_ const TData *getptr(const TKey &p_key) const {
|
|
|
|
- if (unlikely(!hash_table)) {
|
|
|
|
- return nullptr;
|
|
|
|
- }
|
|
|
|
|
|
+ _FORCE_INLINE_ bool has(const TKey &p_key) const {
|
|
|
|
+ uint32_t _pos = 0;
|
|
|
|
+ return _lookup_pos(p_key, _pos);
|
|
|
|
+ }
|
|
|
|
|
|
- const Element *e = const_cast<Element *>(get_element(p_key));
|
|
|
|
|
|
+ bool erase(const TKey &p_key) {
|
|
|
|
+ uint32_t pos = 0;
|
|
|
|
+ bool exists = _lookup_pos(p_key, pos);
|
|
|
|
|
|
- if (e) {
|
|
|
|
- return &e->pair.data;
|
|
|
|
|
|
+ if (!exists) {
|
|
|
|
+ return false;
|
|
}
|
|
}
|
|
|
|
|
|
- return nullptr;
|
|
|
|
- }
|
|
|
|
|
|
+ uint32_t capacity = hash_table_size_primes[capacity_index];
|
|
|
|
+ uint32_t next_pos = (pos + 1) % capacity;
|
|
|
|
+ while (hashes[next_pos] != EMPTY_HASH && _get_probe_length(next_pos, hashes[next_pos], capacity) != 0) {
|
|
|
|
+ SWAP(hashes[next_pos], hashes[pos]);
|
|
|
|
+ SWAP(elements[next_pos], elements[pos]);
|
|
|
|
+ pos = next_pos;
|
|
|
|
+ next_pos = (pos + 1) % capacity;
|
|
|
|
+ }
|
|
|
|
|
|
- /**
|
|
|
|
- * Same as get, except it can return nullptr when item was not found.
|
|
|
|
- * This version is custom, will take a hash and a custom key (that should support operator==()
|
|
|
|
- */
|
|
|
|
|
|
+ hashes[pos] = EMPTY_HASH;
|
|
|
|
|
|
- template <class C>
|
|
|
|
- _FORCE_INLINE_ TData *custom_getptr(C p_custom_key, uint32_t p_custom_hash) {
|
|
|
|
- if (unlikely(!hash_table)) {
|
|
|
|
- return nullptr;
|
|
|
|
|
|
+ if (head_element == elements[pos]) {
|
|
|
|
+ head_element = elements[pos]->next;
|
|
}
|
|
}
|
|
|
|
|
|
- uint32_t hash = p_custom_hash;
|
|
|
|
- uint32_t index = hash & ((1 << hash_table_power) - 1);
|
|
|
|
-
|
|
|
|
- Element *e = hash_table[index];
|
|
|
|
|
|
+ if (tail_element == elements[pos]) {
|
|
|
|
+ tail_element = elements[pos]->prev;
|
|
|
|
+ }
|
|
|
|
|
|
- while (e) {
|
|
|
|
- /* checking hash first avoids comparing key, which may take longer */
|
|
|
|
- if (e->hash == hash && Comparator::compare(e->pair.key, p_custom_key)) {
|
|
|
|
- /* the pair exists in this hashtable, so just update data */
|
|
|
|
- return &e->pair.data;
|
|
|
|
- }
|
|
|
|
|
|
+ if (elements[pos]->prev) {
|
|
|
|
+ elements[pos]->prev->next = elements[pos]->next;
|
|
|
|
+ }
|
|
|
|
|
|
- e = e->next;
|
|
|
|
|
|
+ if (elements[pos]->next) {
|
|
|
|
+ elements[pos]->next->prev = elements[pos]->prev;
|
|
}
|
|
}
|
|
|
|
|
|
- return nullptr;
|
|
|
|
|
|
+ element_alloc.delete_allocation(elements[pos]);
|
|
|
|
+ elements[pos] = nullptr;
|
|
|
|
+
|
|
|
|
+ num_elements--;
|
|
|
|
+ return true;
|
|
}
|
|
}
|
|
|
|
|
|
- template <class C>
|
|
|
|
- _FORCE_INLINE_ const TData *custom_getptr(C p_custom_key, uint32_t p_custom_hash) const {
|
|
|
|
- if (unlikely(!hash_table)) {
|
|
|
|
- return nullptr;
|
|
|
|
|
|
+ // Reserves space for a number of elements, useful to avoid many resizes and rehashes.
|
|
|
|
+ // If adding a known (possibly large) number of elements at once, must be larger than old capacity.
|
|
|
|
+ void reserve(uint32_t p_new_capacity) {
|
|
|
|
+ uint32_t new_index = capacity_index;
|
|
|
|
+
|
|
|
|
+ while (hash_table_size_primes[new_index] < p_new_capacity) {
|
|
|
|
+ ERR_FAIL_COND_MSG(new_index + 1 == (uint32_t)HASH_TABLE_SIZE_MAX, nullptr);
|
|
|
|
+ new_index++;
|
|
}
|
|
}
|
|
|
|
|
|
- uint32_t hash = p_custom_hash;
|
|
|
|
- uint32_t index = hash & ((1 << hash_table_power) - 1);
|
|
|
|
|
|
+ if (new_index == capacity_index) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (elements == nullptr) {
|
|
|
|
+ capacity_index = new_index;
|
|
|
|
+ return; // Unallocated yet.
|
|
|
|
+ }
|
|
|
|
+ _resize_and_rehash(new_index);
|
|
|
|
+ }
|
|
|
|
|
|
- const Element *e = hash_table[index];
|
|
|
|
|
|
+ /** Iterator API **/
|
|
|
|
|
|
- while (e) {
|
|
|
|
- /* checking hash first avoids comparing key, which may take longer */
|
|
|
|
- if (e->hash == hash && Comparator::compare(e->pair.key, p_custom_key)) {
|
|
|
|
- /* the pair exists in this hashtable, so just update data */
|
|
|
|
- return &e->pair.data;
|
|
|
|
|
|
+ struct ConstIterator {
|
|
|
|
+ _FORCE_INLINE_ const KeyValue<TKey, TValue> &operator*() const {
|
|
|
|
+ return E->data;
|
|
|
|
+ }
|
|
|
|
+ _FORCE_INLINE_ const KeyValue<TKey, TValue> *operator->() const { return &E->data; }
|
|
|
|
+ _FORCE_INLINE_ ConstIterator &operator++() {
|
|
|
|
+ if (E) {
|
|
|
|
+ E = E->next;
|
|
}
|
|
}
|
|
-
|
|
|
|
- e = e->next;
|
|
|
|
|
|
+ return *this;
|
|
|
|
+ }
|
|
|
|
+ _FORCE_INLINE_ ConstIterator &operator--() {
|
|
|
|
+ if (E) {
|
|
|
|
+ E = E->prev;
|
|
|
|
+ }
|
|
|
|
+ return *this;
|
|
}
|
|
}
|
|
|
|
|
|
- return nullptr;
|
|
|
|
- }
|
|
|
|
|
|
+ _FORCE_INLINE_ bool operator==(const ConstIterator &b) const { return E == b.E; }
|
|
|
|
+ _FORCE_INLINE_ bool operator!=(const ConstIterator &b) const { return E != b.E; }
|
|
|
|
|
|
- /**
|
|
|
|
- * Erase an item, return true if erasing was successful
|
|
|
|
- */
|
|
|
|
|
|
+ _FORCE_INLINE_ explicit operator bool() const {
|
|
|
|
+ return E != nullptr;
|
|
|
|
+ }
|
|
|
|
|
|
- bool erase(const TKey &p_key) {
|
|
|
|
- if (unlikely(!hash_table)) {
|
|
|
|
- return false;
|
|
|
|
|
|
+ _FORCE_INLINE_ ConstIterator(const HashMapElement<TKey, TValue> *p_E) { E = p_E; }
|
|
|
|
+ _FORCE_INLINE_ ConstIterator() {}
|
|
|
|
+ _FORCE_INLINE_ ConstIterator(const ConstIterator &p_it) { E = p_it.E; }
|
|
|
|
+ _FORCE_INLINE_ void operator=(const ConstIterator &p_it) {
|
|
|
|
+ E = p_it.E;
|
|
}
|
|
}
|
|
|
|
|
|
- uint32_t hash = Hasher::hash(p_key);
|
|
|
|
- uint32_t index = hash & ((1 << hash_table_power) - 1);
|
|
|
|
-
|
|
|
|
- Element *e = hash_table[index];
|
|
|
|
- Element *p = nullptr;
|
|
|
|
- while (e) {
|
|
|
|
- /* checking hash first avoids comparing key, which may take longer */
|
|
|
|
- if (e->hash == hash && Comparator::compare(e->pair.key, p_key)) {
|
|
|
|
- if (p) {
|
|
|
|
- p->next = e->next;
|
|
|
|
- } else {
|
|
|
|
- // begin of list
|
|
|
|
- hash_table[index] = e->next;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- memdelete(e);
|
|
|
|
- elements--;
|
|
|
|
-
|
|
|
|
- if (elements == 0) {
|
|
|
|
- erase_hash_table();
|
|
|
|
- } else {
|
|
|
|
- check_hash_table();
|
|
|
|
- }
|
|
|
|
- return true;
|
|
|
|
|
|
+ private:
|
|
|
|
+ const HashMapElement<TKey, TValue> *E = nullptr;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ struct Iterator {
|
|
|
|
+ _FORCE_INLINE_ KeyValue<TKey, TValue> &operator*() const {
|
|
|
|
+ return E->data;
|
|
|
|
+ }
|
|
|
|
+ _FORCE_INLINE_ KeyValue<TKey, TValue> *operator->() const { return &E->data; }
|
|
|
|
+ _FORCE_INLINE_ Iterator &operator++() {
|
|
|
|
+ if (E) {
|
|
|
|
+ E = E->next;
|
|
|
|
+ }
|
|
|
|
+ return *this;
|
|
|
|
+ }
|
|
|
|
+ _FORCE_INLINE_ Iterator &operator--() {
|
|
|
|
+ if (E) {
|
|
|
|
+ E = E->prev;
|
|
}
|
|
}
|
|
|
|
+ return *this;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ _FORCE_INLINE_ bool operator==(const Iterator &b) const { return E == b.E; }
|
|
|
|
+ _FORCE_INLINE_ bool operator!=(const Iterator &b) const { return E != b.E; }
|
|
|
|
|
|
- p = e;
|
|
|
|
- e = e->next;
|
|
|
|
|
|
+ _FORCE_INLINE_ explicit operator bool() const {
|
|
|
|
+ return E != nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
|
|
+ _FORCE_INLINE_ Iterator(HashMapElement<TKey, TValue> *p_E) { E = p_E; }
|
|
|
|
+ _FORCE_INLINE_ Iterator() {}
|
|
|
|
+ _FORCE_INLINE_ Iterator(const Iterator &p_it) { E = p_it.E; }
|
|
|
|
+ _FORCE_INLINE_ void operator=(const Iterator &p_it) {
|
|
|
|
+ E = p_it.E;
|
|
|
|
+ }
|
|
|
|
|
|
- inline const TData &operator[](const TKey &p_key) const { // constref
|
|
|
|
|
|
+ operator ConstIterator() const {
|
|
|
|
+ return ConstIterator(E);
|
|
|
|
+ }
|
|
|
|
|
|
- return get(p_key);
|
|
|
|
|
|
+ private:
|
|
|
|
+ HashMapElement<TKey, TValue> *E = nullptr;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ _FORCE_INLINE_ Iterator begin() {
|
|
|
|
+ return Iterator(head_element);
|
|
|
|
+ }
|
|
|
|
+ _FORCE_INLINE_ Iterator end() {
|
|
|
|
+ return Iterator(nullptr);
|
|
|
|
+ }
|
|
|
|
+ _FORCE_INLINE_ Iterator last() {
|
|
|
|
+ return Iterator(tail_element);
|
|
}
|
|
}
|
|
- inline TData &operator[](const TKey &p_key) { // assignment
|
|
|
|
|
|
|
|
- Element *e = nullptr;
|
|
|
|
- if (!hash_table) {
|
|
|
|
- make_hash_table(); // if no table, make one
|
|
|
|
- } else {
|
|
|
|
- e = const_cast<Element *>(get_element(p_key));
|
|
|
|
|
|
+ _FORCE_INLINE_ Iterator find(const TKey &p_key) {
|
|
|
|
+ uint32_t pos = 0;
|
|
|
|
+ bool exists = _lookup_pos(p_key, pos);
|
|
|
|
+ if (!exists) {
|
|
|
|
+ return end();
|
|
}
|
|
}
|
|
|
|
+ return Iterator(elements[pos]);
|
|
|
|
+ }
|
|
|
|
|
|
- /* if we made it up to here, the pair doesn't exist, create */
|
|
|
|
- if (!e) {
|
|
|
|
- e = create_element(p_key);
|
|
|
|
- CRASH_COND(!e);
|
|
|
|
- check_hash_table(); // perform mantenience routine
|
|
|
|
|
|
+ _FORCE_INLINE_ void remove(const Iterator &p_iter) {
|
|
|
|
+ if (p_iter) {
|
|
|
|
+ erase(p_iter->key);
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- return e->pair.data;
|
|
|
|
|
|
+ _FORCE_INLINE_ ConstIterator begin() const {
|
|
|
|
+ return ConstIterator(head_element);
|
|
|
|
+ }
|
|
|
|
+ _FORCE_INLINE_ ConstIterator end() const {
|
|
|
|
+ return ConstIterator(nullptr);
|
|
|
|
+ }
|
|
|
|
+ _FORCE_INLINE_ ConstIterator last() const {
|
|
|
|
+ return ConstIterator(tail_element);
|
|
}
|
|
}
|
|
|
|
|
|
- /**
|
|
|
|
- * Get the next key to p_key, and the first key if p_key is null.
|
|
|
|
- * Returns a pointer to the next key if found, nullptr otherwise.
|
|
|
|
- * Adding/Removing elements while iterating will, of course, have unexpected results, don't do it.
|
|
|
|
- *
|
|
|
|
- * Example:
|
|
|
|
- *
|
|
|
|
- * const TKey *k=nullptr;
|
|
|
|
- *
|
|
|
|
- * while( (k=table.next(k)) ) {
|
|
|
|
- *
|
|
|
|
- * print( *k );
|
|
|
|
- * }
|
|
|
|
- *
|
|
|
|
- */
|
|
|
|
- const TKey *next(const TKey *p_key) const {
|
|
|
|
- if (unlikely(!hash_table)) {
|
|
|
|
- return nullptr;
|
|
|
|
|
|
+ _FORCE_INLINE_ ConstIterator find(const TKey &p_key) const {
|
|
|
|
+ uint32_t pos = 0;
|
|
|
|
+ bool exists = _lookup_pos(p_key, pos);
|
|
|
|
+ if (!exists) {
|
|
|
|
+ return end();
|
|
}
|
|
}
|
|
|
|
+ return ConstIterator(elements[pos]);
|
|
|
|
+ }
|
|
|
|
|
|
- if (!p_key) { /* get the first key */
|
|
|
|
-
|
|
|
|
- for (int i = 0; i < (1 << hash_table_power); i++) {
|
|
|
|
- if (hash_table[i]) {
|
|
|
|
- return &hash_table[i]->pair.key;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- } else { /* get the next key */
|
|
|
|
|
|
+ /* Indexing */
|
|
|
|
|
|
- const Element *e = get_element(*p_key);
|
|
|
|
- ERR_FAIL_COND_V_MSG(!e, nullptr, "Invalid key supplied.");
|
|
|
|
- if (e->next) {
|
|
|
|
- /* if there is a "next" in the list, return that */
|
|
|
|
- return &e->next->pair.key;
|
|
|
|
- } else {
|
|
|
|
- /* go to next elements */
|
|
|
|
- uint32_t index = e->hash & ((1 << hash_table_power) - 1);
|
|
|
|
- index++;
|
|
|
|
- for (int i = index; i < (1 << hash_table_power); i++) {
|
|
|
|
- if (hash_table[i]) {
|
|
|
|
- return &hash_table[i]->pair.key;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ const TValue &operator[](const TKey &p_key) const {
|
|
|
|
+ uint32_t pos = 0;
|
|
|
|
+ bool exists = _lookup_pos(p_key, pos);
|
|
|
|
+ CRASH_COND(!exists);
|
|
|
|
+ return elements[pos]->data.value;
|
|
|
|
+ }
|
|
|
|
|
|
- /* nothing found, was at end */
|
|
|
|
|
|
+ TValue &operator[](const TKey &p_key) {
|
|
|
|
+ uint32_t pos = 0;
|
|
|
|
+ bool exists = _lookup_pos(p_key, pos);
|
|
|
|
+ if (!exists) {
|
|
|
|
+ return _insert(p_key, TValue())->data.value;
|
|
|
|
+ } else {
|
|
|
|
+ return elements[pos]->data.value;
|
|
}
|
|
}
|
|
-
|
|
|
|
- return nullptr; /* nothing found */
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- inline unsigned int size() const {
|
|
|
|
- return elements;
|
|
|
|
- }
|
|
|
|
|
|
+ /* Insert */
|
|
|
|
|
|
- inline bool is_empty() const {
|
|
|
|
- return elements == 0;
|
|
|
|
|
|
+ Iterator insert(const TKey &p_key, const TValue &p_value, bool p_front_insert = false) {
|
|
|
|
+ return Iterator(_insert(p_key, p_value, p_front_insert));
|
|
}
|
|
}
|
|
|
|
|
|
- void clear() {
|
|
|
|
- /* clean up */
|
|
|
|
- if (hash_table) {
|
|
|
|
- for (int i = 0; i < (1 << hash_table_power); i++) {
|
|
|
|
- while (hash_table[i]) {
|
|
|
|
- Element *e = hash_table[i];
|
|
|
|
- hash_table[i] = e->next;
|
|
|
|
- memdelete(e);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ /* Constructors */
|
|
|
|
|
|
- memdelete_arr(hash_table);
|
|
|
|
|
|
+ HashMap(const HashMap &p_other) {
|
|
|
|
+ reserve(hash_table_size_primes[p_other.capacity_index]);
|
|
|
|
+
|
|
|
|
+ if (p_other.num_elements == 0) {
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
|
|
|
|
- hash_table = nullptr;
|
|
|
|
- hash_table_power = 0;
|
|
|
|
- elements = 0;
|
|
|
|
|
|
+ for (const KeyValue<TKey, TValue> &E : p_other) {
|
|
|
|
+ insert(E.key, E.value);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- void operator=(const HashMap &p_table) {
|
|
|
|
- copy_from(p_table);
|
|
|
|
- }
|
|
|
|
|
|
+ void operator=(const HashMap &p_other) {
|
|
|
|
+ if (this == &p_other) {
|
|
|
|
+ return; // Ignore self assignment.
|
|
|
|
+ }
|
|
|
|
+ if (num_elements != 0) {
|
|
|
|
+ clear();
|
|
|
|
+ }
|
|
|
|
|
|
- void get_key_list(List<TKey> *r_keys) const {
|
|
|
|
- if (unlikely(!hash_table)) {
|
|
|
|
- return;
|
|
|
|
|
|
+ reserve(hash_table_size_primes[p_other.capacity_index]);
|
|
|
|
+
|
|
|
|
+ if (p_other.elements == nullptr) {
|
|
|
|
+ return; // Nothing to copy.
|
|
}
|
|
}
|
|
- for (int i = 0; i < (1 << hash_table_power); i++) {
|
|
|
|
- Element *e = hash_table[i];
|
|
|
|
- while (e) {
|
|
|
|
- r_keys->push_back(e->pair.key);
|
|
|
|
- e = e->next;
|
|
|
|
- }
|
|
|
|
|
|
+
|
|
|
|
+ for (const KeyValue<TKey, TValue> &E : p_other) {
|
|
|
|
+ insert(E.key, E.value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- HashMap() {}
|
|
|
|
|
|
+ HashMap(uint32_t p_initial_capacity) {
|
|
|
|
+ // Capacity can't be 0.
|
|
|
|
+ capacity_index = 0;
|
|
|
|
+ reserve(p_initial_capacity);
|
|
|
|
+ }
|
|
|
|
+ HashMap() {
|
|
|
|
+ capacity_index = MIN_CAPACITY_INDEX;
|
|
|
|
+ }
|
|
|
|
|
|
- HashMap(const HashMap &p_table) {
|
|
|
|
- copy_from(p_table);
|
|
|
|
|
|
+ uint32_t debug_get_hash(uint32_t p_index) {
|
|
|
|
+ if (num_elements == 0) {
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ ERR_FAIL_INDEX_V(p_index, get_capacity(), 0);
|
|
|
|
+ return hashes[p_index];
|
|
|
|
+ }
|
|
|
|
+ Iterator debug_get_element(uint32_t p_index) {
|
|
|
|
+ if (num_elements == 0) {
|
|
|
|
+ return Iterator();
|
|
|
|
+ }
|
|
|
|
+ ERR_FAIL_INDEX_V(p_index, get_capacity(), Iterator());
|
|
|
|
+ return Iterator(elements[p_index]);
|
|
}
|
|
}
|
|
|
|
|
|
~HashMap() {
|
|
~HashMap() {
|
|
clear();
|
|
clear();
|
|
|
|
+
|
|
|
|
+ if (elements != nullptr) {
|
|
|
|
+ Memory::free_static(elements);
|
|
|
|
+ Memory::free_static(hashes);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
} // namespace godot
|
|
} // namespace godot
|
|
|
|
|
|
-#endif // ! HASH_MAP_HPP
|
|
|
|
|
|
+#endif // HASH_MAP_HPP
|