diff --git a/include/godot_cpp/core/memory.hpp b/include/godot_cpp/core/memory.hpp index cf3f193ea..e74c23c52 100644 --- a/include/godot_cpp/core/memory.hpp +++ b/include/godot_cpp/core/memory.hpp @@ -71,6 +71,7 @@ inline constexpr size_t ELEMENT_OFFSET = get_aligned_address(SIZE_OFFSET + sizeo inline constexpr size_t DATA_OFFSET = get_aligned_address(ELEMENT_OFFSET + sizeof(uint64_t), MAX_ALIGN); void *alloc_static(size_t p_bytes, bool p_pad_align = false); +void *alloc_static_zeroed(size_t p_bytes, bool p_pad_align = false); void *realloc_static(void *p_memory, size_t p_bytes, bool p_pad_align = false); void free_static(void *p_ptr, bool p_pad_align = false); }; //namespace Memory diff --git a/include/godot_cpp/templates/hash_map.hpp b/include/godot_cpp/templates/hash_map.hpp index a0a8cadab..b6d6de01c 100644 --- a/include/godot_cpp/templates/hash_map.hpp +++ b/include/godot_cpp/templates/hash_map.hpp @@ -30,11 +30,14 @@ #pragma once +#include #include #include #include #include +#include + namespace godot { /** @@ -61,29 +64,27 @@ struct HashMapElement { data(p_key, p_value) {} }; -bool _hashmap_variant_less_than(const Variant &p_left, const Variant &p_right); - template , typename Allocator = DefaultTypedAllocator>> -class HashMap { +class HashMap : private Allocator { public: static constexpr uint32_t MIN_CAPACITY_INDEX = 2; // Use a prime. static constexpr float MAX_OCCUPANCY = 0.75; static constexpr uint32_t EMPTY_HASH = 0; + using KV = KeyValue; // Type alias for easier access to KeyValue. private: - Allocator element_alloc; - HashMapElement **elements = nullptr; - uint32_t *hashes = nullptr; - HashMapElement *head_element = nullptr; - HashMapElement *tail_element = nullptr; + HashMapElement **_elements = nullptr; + uint32_t *_hashes = nullptr; + HashMapElement *_head_element = nullptr; + HashMapElement *_tail_element = nullptr; - uint32_t capacity_index = 0; - uint32_t num_elements = 0; + uint32_t _capacity_idx = 0; + uint32_t _size = 0; - _FORCE_INLINE_ uint32_t _hash(const TKey &p_key) const { + _FORCE_INLINE_ static uint32_t _hash(const TKey &p_key) { uint32_t hash = Hasher::hash(p_key); if (unlikely(hash == EMPTY_HASH)) { @@ -93,91 +94,98 @@ class HashMap { return hash; } - static _FORCE_INLINE_ uint32_t _get_probe_length(const uint32_t p_pos, const uint32_t p_hash, const uint32_t p_capacity, const uint64_t p_capacity_inv) { - const uint32_t original_pos = fastmod(p_hash, p_capacity_inv, p_capacity); - return fastmod(p_pos - original_pos + p_capacity, p_capacity_inv, p_capacity); + _FORCE_INLINE_ static constexpr void _increment_mod(uint32_t &r_idx, const uint32_t p_capacity) { + r_idx++; + // `if` is faster than both fastmod and mod. + if (unlikely(r_idx == p_capacity)) { + r_idx = 0; + } + } + + static _FORCE_INLINE_ uint32_t _get_probe_length(const uint32_t p_idx, const uint32_t p_hash, const uint32_t p_capacity, const uint64_t p_capacity_inv) { + const uint32_t original_idx = fastmod(p_hash, p_capacity_inv, p_capacity); + const uint32_t distance_idx = p_idx - original_idx + p_capacity; + // At most p_capacity over 0, so we can use an if (faster than fastmod). + return distance_idx >= p_capacity ? distance_idx - p_capacity : distance_idx; } - bool _lookup_pos(const TKey &p_key, uint32_t &r_pos) const { - if (elements == nullptr || num_elements == 0) { - return false; // Failed lookups, no elements - } + bool _lookup_idx(const TKey &p_key, uint32_t &r_idx) const { + return _elements != nullptr && _size > 0 && _lookup_idx_unchecked(p_key, _hash(p_key), r_idx); + } - const uint32_t capacity = hash_table_size_primes[capacity_index]; - const uint64_t capacity_inv = hash_table_size_primes_inv[capacity_index]; - uint32_t hash = _hash(p_key); - uint32_t pos = fastmod(hash, capacity_inv, capacity); + /// Note: Assumes that _elements != nullptr + bool _lookup_idx_unchecked(const TKey &p_key, uint32_t p_hash, uint32_t &r_idx) const { + const uint32_t capacity = hash_table_size_primes[_capacity_idx]; + const uint64_t capacity_inv = hash_table_size_primes_inv[_capacity_idx]; + uint32_t idx = fastmod(p_hash, capacity_inv, capacity); uint32_t distance = 0; while (true) { - if (hashes[pos] == EMPTY_HASH) { + if (_hashes[idx] == EMPTY_HASH) { return false; } - if (distance > _get_probe_length(pos, hashes[pos], capacity, capacity_inv)) { + if (distance > _get_probe_length(idx, _hashes[idx], capacity, capacity_inv)) { return false; } - if (hashes[pos] == hash && Comparator::compare(elements[pos]->data.key, p_key)) { - r_pos = pos; + if (_hashes[idx] == p_hash && Comparator::compare(_elements[idx]->data.key, p_key)) { + r_idx = idx; return true; } - pos = fastmod((pos + 1), capacity_inv, capacity); + _increment_mod(idx, capacity); distance++; } } - void _insert_with_hash(uint32_t p_hash, HashMapElement *p_value) { - const uint32_t capacity = hash_table_size_primes[capacity_index]; - const uint64_t capacity_inv = hash_table_size_primes_inv[capacity_index]; + void _insert_element(uint32_t p_hash, HashMapElement *p_value) { + const uint32_t capacity = hash_table_size_primes[_capacity_idx]; + const uint64_t capacity_inv = hash_table_size_primes_inv[_capacity_idx]; uint32_t hash = p_hash; HashMapElement *value = p_value; uint32_t distance = 0; - uint32_t pos = fastmod(hash, capacity_inv, capacity); + uint32_t idx = fastmod(hash, capacity_inv, capacity); while (true) { - if (hashes[pos] == EMPTY_HASH) { - elements[pos] = value; - hashes[pos] = hash; + if (_hashes[idx] == EMPTY_HASH) { + _elements[idx] = value; + _hashes[idx] = hash; - num_elements++; + _size++; return; } // 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, capacity_inv); + uint32_t existing_probe_len = _get_probe_length(idx, _hashes[idx], capacity, capacity_inv); if (existing_probe_len < distance) { - SWAP(hash, hashes[pos]); - SWAP(value, elements[pos]); + SWAP(hash, _hashes[idx]); + SWAP(value, _elements[idx]); distance = existing_probe_len; } - pos = fastmod((pos + 1), capacity_inv, capacity); + _increment_mod(idx, capacity); distance++; } } - void _resize_and_rehash(uint32_t p_new_capacity_index) { - uint32_t old_capacity = hash_table_size_primes[capacity_index]; + void _resize_and_rehash(uint32_t p_new_capacity_idx) { + uint32_t old_capacity = hash_table_size_primes[_capacity_idx]; // Capacity can't be 0. - capacity_index = MAX((uint32_t)MIN_CAPACITY_INDEX, p_new_capacity_index); + _capacity_idx = MAX((uint32_t)MIN_CAPACITY_INDEX, p_new_capacity_idx); - uint32_t capacity = hash_table_size_primes[capacity_index]; + uint32_t capacity = hash_table_size_primes[_capacity_idx]; - HashMapElement **old_elements = elements; - uint32_t *old_hashes = hashes; + HashMapElement **old_elements = _elements; + uint32_t *old_hashes = _hashes; - num_elements = 0; - hashes = reinterpret_cast(Memory::alloc_static(sizeof(uint32_t) * capacity)); - elements = reinterpret_cast **>(Memory::alloc_static(sizeof(HashMapElement *) * capacity)); + _size = 0; + static_assert(EMPTY_HASH == 0, "Assuming EMPTY_HASH = 0 for alloc_static_zeroed call"); - for (uint32_t i = 0; i < capacity; i++) { - hashes[i] = 0; - elements[i] = nullptr; - } + _hashes = reinterpret_cast(Memory::alloc_static_zeroed(sizeof(uint32_t) * capacity)); + _elements = reinterpret_cast **>(Memory::alloc_static(sizeof(HashMapElement *) * capacity)); if (old_capacity == 0) { // Nothing to do. @@ -189,243 +197,207 @@ class HashMap { continue; } - _insert_with_hash(old_hashes[i], old_elements[i]); + _insert_element(old_hashes[i], old_elements[i]); } Memory::free_static(old_elements); Memory::free_static(old_hashes); } - _FORCE_INLINE_ HashMapElement *_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)) { + _FORCE_INLINE_ HashMapElement *_insert(const TKey &p_key, const TValue &p_value, uint32_t p_hash, bool p_front_insert = false) { + uint32_t capacity = hash_table_size_primes[_capacity_idx]; + if (unlikely(_elements == nullptr)) { // Allocate on demand to save memory. - hashes = reinterpret_cast(Memory::alloc_static(sizeof(uint32_t) * capacity)); - elements = reinterpret_cast **>(Memory::alloc_static(sizeof(HashMapElement *) * capacity)); + static_assert(EMPTY_HASH == 0, "Assuming EMPTY_HASH = 0 for alloc_static_zeroed call"); + _hashes = reinterpret_cast(Memory::alloc_static_zeroed(sizeof(uint32_t) * capacity)); + _elements = reinterpret_cast **>(Memory::alloc_static(sizeof(HashMapElement *) * capacity)); + } - for (uint32_t i = 0; i < capacity; i++) { - hashes[i] = EMPTY_HASH; - elements[i] = nullptr; - } + if (_size + 1 > MAX_OCCUPANCY * capacity) { + ERR_FAIL_COND_V_MSG(_capacity_idx + 1 == HASH_TABLE_SIZE_MAX, nullptr, "Hash table maximum capacity reached, aborting insertion."); + _resize_and_rehash(_capacity_idx + 1); } - uint32_t pos = 0; - bool exists = _lookup_pos(p_key, pos); + HashMapElement *elem = Allocator::new_allocation(HashMapElement(p_key, p_value)); - if (exists) { - elements[pos]->data.value = p_value; - return elements[pos]; + 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 { - 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); - } + _tail_element->next = elem; + elem->prev = _tail_element; + _tail_element = elem; + } - HashMapElement *elem = element_alloc.new_allocation(HashMapElement(p_key, p_value)); - - 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; - } + _insert_element(p_hash, elem); + return elem; + } - uint32_t hash = _hash(p_key); - _insert_with_hash(hash, elem); - return elem; + void _clear_data() { + HashMapElement *current = _tail_element; + while (current != nullptr) { + HashMapElement *prev = current->prev; + Allocator::delete_allocation(current); + current = prev; } } public: - _FORCE_INLINE_ uint32_t get_capacity() const { return hash_table_size_primes[capacity_index]; } - _FORCE_INLINE_ uint32_t size() const { return num_elements; } + _FORCE_INLINE_ uint32_t get_capacity() const { return hash_table_size_primes[_capacity_idx]; } + _FORCE_INLINE_ uint32_t size() const { return _size; } /* Standard Godot Container API */ bool is_empty() const { - return num_elements == 0; + return _size == 0; } void clear() { - if (elements == nullptr || num_elements == 0) { + if (_elements == nullptr || _size == 0) { return; } - uint32_t capacity = hash_table_size_primes[capacity_index]; - for (uint32_t i = 0; i < capacity; i++) { - if (hashes[i] == EMPTY_HASH) { - continue; - } - hashes[i] = EMPTY_HASH; - element_alloc.delete_allocation(elements[i]); - elements[i] = nullptr; - } + _clear_data(); + memset(_hashes, EMPTY_HASH, get_capacity() * sizeof(uint32_t)); - tail_element = nullptr; - head_element = nullptr; - num_elements = 0; + _tail_element = nullptr; + _head_element = nullptr; + _size = 0; } void sort() { - if (elements == nullptr || num_elements < 2) { - return; // An empty or single element HashMap is already sorted. - } - // Use insertion sort because we want this operation to be fast for the - // common case where the input is already sorted or nearly sorted. - HashMapElement *inserting = head_element->next; - while (inserting != nullptr) { - HashMapElement *after = nullptr; - for (HashMapElement *current = inserting->prev; current != nullptr; current = current->prev) { - if (_hashmap_variant_less_than(inserting->data.key, current->data.key)) { - after = current; - } else { - break; - } - } - HashMapElement *next = inserting->next; - if (after != nullptr) { - // Modify the elements around `inserting` to remove it from its current position. - inserting->prev->next = next; - if (next == nullptr) { - tail_element = inserting->prev; - } else { - next->prev = inserting->prev; - } - // Modify `before` and `after` to insert `inserting` between them. - HashMapElement *before = after->prev; - if (before == nullptr) { - head_element = inserting; - } else { - before->next = inserting; - } - after->prev = inserting; - // Point `inserting` to its new surroundings. - inserting->prev = before; - inserting->next = after; - } - inserting = next; + sort_custom>(); + } + + template + void sort_custom() { + if (size() < 2) { + return; } + + using E = HashMapElement; + SortList, &E::data, &E::prev, &E::next, C> sorter; + sorter.sort(_head_element, _tail_element); } - TValue &get(const TKey &p_key) { - uint32_t pos = 0; - bool exists = _lookup_pos(p_key, pos); + TValue &get(const TKey &p_key) _LIFETIME_BOUND_ { + uint32_t idx = 0; + bool exists = _lookup_idx(p_key, idx); CRASH_COND_MSG(!exists, "HashMap key not found."); - return elements[pos]->data.value; + return _elements[idx]->data.value; } - const TValue &get(const TKey &p_key) const { - uint32_t pos = 0; - bool exists = _lookup_pos(p_key, pos); + const TValue &get(const TKey &p_key) const _LIFETIME_BOUND_ { + uint32_t idx = 0; + bool exists = _lookup_idx(p_key, idx); CRASH_COND_MSG(!exists, "HashMap key not found."); - return elements[pos]->data.value; + return _elements[idx]->data.value; } - const TValue *getptr(const TKey &p_key) const { - uint32_t pos = 0; - bool exists = _lookup_pos(p_key, pos); + const TValue *getptr(const TKey &p_key) const _LIFETIME_BOUND_ { + uint32_t idx = 0; + bool exists = _lookup_idx(p_key, idx); if (exists) { - return &elements[pos]->data.value; + return &_elements[idx]->data.value; } return nullptr; } - TValue *getptr(const TKey &p_key) { - uint32_t pos = 0; - bool exists = _lookup_pos(p_key, pos); + TValue *getptr(const TKey &p_key) _LIFETIME_BOUND_ { + uint32_t idx = 0; + bool exists = _lookup_idx(p_key, idx); if (exists) { - return &elements[pos]->data.value; + return &_elements[idx]->data.value; } return nullptr; } _FORCE_INLINE_ bool has(const TKey &p_key) const { - uint32_t _pos = 0; - return _lookup_pos(p_key, _pos); + uint32_t _idx = 0; + return _lookup_idx(p_key, _idx); } bool erase(const TKey &p_key) { - uint32_t pos = 0; - bool exists = _lookup_pos(p_key, pos); + uint32_t idx = 0; + bool exists = _lookup_idx(p_key, idx); if (!exists) { return false; } - const uint32_t capacity = hash_table_size_primes[capacity_index]; - const uint64_t capacity_inv = hash_table_size_primes_inv[capacity_index]; - uint32_t next_pos = fastmod((pos + 1), capacity_inv, capacity); - while (hashes[next_pos] != EMPTY_HASH && _get_probe_length(next_pos, hashes[next_pos], capacity, capacity_inv) != 0) { - SWAP(hashes[next_pos], hashes[pos]); - SWAP(elements[next_pos], elements[pos]); - pos = next_pos; - next_pos = fastmod((pos + 1), capacity_inv, capacity); + const uint32_t capacity = hash_table_size_primes[_capacity_idx]; + const uint64_t capacity_inv = hash_table_size_primes_inv[_capacity_idx]; + uint32_t next_idx = fastmod((idx + 1), capacity_inv, capacity); + while (_hashes[next_idx] != EMPTY_HASH && _get_probe_length(next_idx, _hashes[next_idx], capacity, capacity_inv) != 0) { + SWAP(_hashes[next_idx], _hashes[idx]); + SWAP(_elements[next_idx], _elements[idx]); + idx = next_idx; + _increment_mod(next_idx, capacity); } - hashes[pos] = EMPTY_HASH; + _hashes[idx] = EMPTY_HASH; - if (head_element == elements[pos]) { - head_element = elements[pos]->next; + if (_head_element == _elements[idx]) { + _head_element = _elements[idx]->next; } - if (tail_element == elements[pos]) { - tail_element = elements[pos]->prev; + if (_tail_element == _elements[idx]) { + _tail_element = _elements[idx]->prev; } - if (elements[pos]->prev) { - elements[pos]->prev->next = elements[pos]->next; + if (_elements[idx]->prev) { + _elements[idx]->prev->next = _elements[idx]->next; } - if (elements[pos]->next) { - elements[pos]->next->prev = elements[pos]->prev; + if (_elements[idx]->next) { + _elements[idx]->next->prev = _elements[idx]->prev; } - element_alloc.delete_allocation(elements[pos]); - elements[pos] = nullptr; + Allocator::delete_allocation(_elements[idx]); - num_elements--; + _size--; return true; } // Replace the key of an entry in-place, without invalidating iterators or changing the entries position during iteration. // p_old_key must exist in the map and p_new_key must not, unless it is equal to p_old_key. bool replace_key(const TKey &p_old_key, const TKey &p_new_key) { + ERR_FAIL_COND_V(_elements == nullptr || _size == 0, false); if (p_old_key == p_new_key) { return true; } - uint32_t pos = 0; - ERR_FAIL_COND_V(_lookup_pos(p_new_key, pos), false); - ERR_FAIL_COND_V(!_lookup_pos(p_old_key, pos), false); - HashMapElement *element = elements[pos]; - - // Delete the old entries in hashes and elements. - const uint32_t capacity = hash_table_size_primes[capacity_index]; - const uint64_t capacity_inv = hash_table_size_primes_inv[capacity_index]; - uint32_t next_pos = fastmod((pos + 1), capacity_inv, capacity); - while (hashes[next_pos] != EMPTY_HASH && _get_probe_length(next_pos, hashes[next_pos], capacity, capacity_inv) != 0) { - SWAP(hashes[next_pos], hashes[pos]); - SWAP(elements[next_pos], elements[pos]); - pos = next_pos; - next_pos = fastmod((pos + 1), capacity_inv, capacity); - } - hashes[pos] = EMPTY_HASH; - elements[pos] = nullptr; - // _insert_with_hash will increment this again. - num_elements--; + const uint32_t new_hash = _hash(p_new_key); + uint32_t idx = 0; + ERR_FAIL_COND_V(_lookup_idx_unchecked(p_new_key, new_hash, idx), false); + ERR_FAIL_COND_V(!_lookup_idx(p_old_key, idx), false); + HashMapElement *element = _elements[idx]; + + // Delete the old entries in _hashes and _elements. + const uint32_t capacity = hash_table_size_primes[_capacity_idx]; + const uint64_t capacity_inv = hash_table_size_primes_inv[_capacity_idx]; + uint32_t next_idx = fastmod((idx + 1), capacity_inv, capacity); + while (_hashes[next_idx] != EMPTY_HASH && _get_probe_length(next_idx, _hashes[next_idx], capacity, capacity_inv) != 0) { + SWAP(_hashes[next_idx], _hashes[idx]); + SWAP(_elements[next_idx], _elements[idx]); + idx = next_idx; + _increment_mod(next_idx, capacity); + } + + _hashes[idx] = EMPTY_HASH; + + // _insert_element will increment this again. + _size--; // Update the HashMapElement with the new key and reinsert it. const_cast(element->data.key) = p_new_key; - uint32_t hash = _hash(p_new_key); - _insert_with_hash(hash, element); + _insert_element(new_hash, element); return true; } @@ -433,22 +405,25 @@ class HashMap { // 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; + uint32_t new_idx = _capacity_idx; - 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++; + while (hash_table_size_primes[new_idx] < p_new_capacity) { + ERR_FAIL_COND_MSG(new_idx + 1 == (uint32_t)HASH_TABLE_SIZE_MAX, nullptr); + new_idx++; } - if (new_index == capacity_index) { + if (new_idx == _capacity_idx) { + if (p_new_capacity < _size) { + WARN_VERBOSE("reserve() called with a capacity smaller than the current size. This is likely a mistake."); + } return; } - if (elements == nullptr) { - capacity_index = new_index; + if (_elements == nullptr) { + _capacity_idx = new_idx; return; // Unallocated yet. } - _resize_and_rehash(new_index); + _resize_and_rehash(new_idx); } /** Iterator API **/ @@ -529,23 +504,23 @@ class HashMap { HashMapElement *E = nullptr; }; - _FORCE_INLINE_ Iterator begin() { - return Iterator(head_element); + _FORCE_INLINE_ Iterator begin() _LIFETIME_BOUND_ { + return Iterator(_head_element); } - _FORCE_INLINE_ Iterator end() { + _FORCE_INLINE_ Iterator end() _LIFETIME_BOUND_ { return Iterator(nullptr); } - _FORCE_INLINE_ Iterator last() { - return Iterator(tail_element); + _FORCE_INLINE_ Iterator last() _LIFETIME_BOUND_ { + return Iterator(_tail_element); } - _FORCE_INLINE_ Iterator find(const TKey &p_key) { - uint32_t pos = 0; - bool exists = _lookup_pos(p_key, pos); + _FORCE_INLINE_ Iterator find(const TKey &p_key) _LIFETIME_BOUND_ { + uint32_t idx = 0; + bool exists = _lookup_idx(p_key, idx); if (!exists) { return end(); } - return Iterator(elements[pos]); + return Iterator(_elements[idx]); } _FORCE_INLINE_ void remove(const Iterator &p_iter) { @@ -554,56 +529,65 @@ class HashMap { } } - _FORCE_INLINE_ ConstIterator begin() const { - return ConstIterator(head_element); + _FORCE_INLINE_ ConstIterator begin() const _LIFETIME_BOUND_ { + return ConstIterator(_head_element); } - _FORCE_INLINE_ ConstIterator end() const { + _FORCE_INLINE_ ConstIterator end() const _LIFETIME_BOUND_ { return ConstIterator(nullptr); } - _FORCE_INLINE_ ConstIterator last() const { - return ConstIterator(tail_element); + _FORCE_INLINE_ ConstIterator last() const _LIFETIME_BOUND_ { + return ConstIterator(_tail_element); } - _FORCE_INLINE_ ConstIterator find(const TKey &p_key) const { - uint32_t pos = 0; - bool exists = _lookup_pos(p_key, pos); + _FORCE_INLINE_ ConstIterator find(const TKey &p_key) const _LIFETIME_BOUND_ { + uint32_t idx = 0; + bool exists = _lookup_idx(p_key, idx); if (!exists) { return end(); } - return ConstIterator(elements[pos]); + return ConstIterator(_elements[idx]); } /* Indexing */ - const TValue &operator[](const TKey &p_key) const { - uint32_t pos = 0; - bool exists = _lookup_pos(p_key, pos); + const TValue &operator[](const TKey &p_key) const _LIFETIME_BOUND_ { + uint32_t idx = 0; + bool exists = _lookup_idx(p_key, idx); CRASH_COND(!exists); - return elements[pos]->data.value; + return _elements[idx]->data.value; } - TValue &operator[](const TKey &p_key) { - uint32_t pos = 0; - bool exists = _lookup_pos(p_key, pos); + TValue &operator[](const TKey &p_key) _LIFETIME_BOUND_ { + const uint32_t hash = _hash(p_key); + uint32_t idx = 0; + bool exists = _elements && _size > 0 && _lookup_idx_unchecked(p_key, hash, idx); if (!exists) { - return _insert(p_key, TValue())->data.value; + return _insert(p_key, TValue(), hash)->data.value; } else { - return elements[pos]->data.value; + return _elements[idx]->data.value; } } /* Insert */ 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)); + const uint32_t hash = _hash(p_key); + uint32_t idx = 0; + bool exists = _elements && _size > 0 && _lookup_idx_unchecked(p_key, hash, idx); + if (!exists) { + return Iterator(_insert(p_key, p_value, hash, p_front_insert)); + } else { + _elements[idx]->data.value = p_value; + return Iterator(_elements[idx]); + } } /* Constructors */ - HashMap(const HashMap &p_other) { - reserve(hash_table_size_primes[p_other.capacity_index]); + explicit HashMap(const HashMap &p_other) { + reserve(hash_table_size_primes[p_other._capacity_idx]); - if (p_other.num_elements == 0) { + if (p_other._size == 0) { return; } @@ -612,17 +596,33 @@ class HashMap { } } + HashMap(HashMap &&p_other) { + _elements = p_other._elements; + _hashes = p_other._hashes; + _head_element = p_other._head_element; + _tail_element = p_other._tail_element; + _capacity_idx = p_other._capacity_idx; + _size = p_other._size; + + p_other._elements = nullptr; + p_other._hashes = nullptr; + p_other._head_element = nullptr; + p_other._tail_element = nullptr; + p_other._capacity_idx = MIN_CAPACITY_INDEX; + p_other._size = 0; + } + void operator=(const HashMap &p_other) { if (this == &p_other) { return; // Ignore self assignment. } - if (num_elements != 0) { + if (_size != 0) { clear(); } - reserve(hash_table_size_primes[p_other.capacity_index]); + reserve(hash_table_size_primes[p_other._capacity_idx]); - if (p_other.elements == nullptr) { + if (p_other._elements == nullptr) { return; // Nothing to copy. } @@ -631,13 +631,43 @@ class HashMap { } } + HashMap &operator=(HashMap &&p_other) { + if (this == &p_other) { + return *this; + } + + if (_size != 0) { + clear(); + } + if (_elements != nullptr) { + Memory::free_static(_elements); + Memory::free_static(_hashes); + } + + _elements = p_other._elements; + _hashes = p_other._hashes; + _head_element = p_other._head_element; + _tail_element = p_other._tail_element; + _capacity_idx = p_other._capacity_idx; + _size = p_other._size; + + p_other._elements = nullptr; + p_other._hashes = nullptr; + p_other._head_element = nullptr; + p_other._tail_element = nullptr; + p_other._capacity_idx = MIN_CAPACITY_INDEX; + p_other._size = 0; + + return *this; + } + HashMap(uint32_t p_initial_capacity) { // Capacity can't be 0. - capacity_index = 0; + _capacity_idx = 0; reserve(p_initial_capacity); } HashMap() { - capacity_index = MIN_CAPACITY_INDEX; + _capacity_idx = MIN_CAPACITY_INDEX; } HashMap(std::initializer_list> p_init) { @@ -647,27 +677,27 @@ class HashMap { } } - uint32_t debug_get_hash(uint32_t p_index) { - if (num_elements == 0) { + uint32_t debug_get_hash(uint32_t p_idx) { + if (_size == 0) { return 0; } - ERR_FAIL_INDEX_V(p_index, get_capacity(), 0); - return hashes[p_index]; + ERR_FAIL_INDEX_V(p_idx, get_capacity(), 0); + return _hashes[p_idx]; } - Iterator debug_get_element(uint32_t p_index) { - if (num_elements == 0) { + Iterator debug_get_element(uint32_t p_idx) { + if (_size == 0) { return Iterator(); } - ERR_FAIL_INDEX_V(p_index, get_capacity(), Iterator()); - return Iterator(elements[p_index]); + ERR_FAIL_INDEX_V(p_idx, get_capacity(), Iterator()); + return Iterator(_elements[p_idx]); } ~HashMap() { - clear(); + _clear_data(); - if (elements != nullptr) { - Memory::free_static(elements); - Memory::free_static(hashes); + if (_elements != nullptr) { + Memory::free_static(_elements); + Memory::free_static(_hashes); } } }; diff --git a/include/godot_cpp/templates/sort_list.h b/include/godot_cpp/templates/sort_list.h new file mode 100644 index 000000000..e53f01360 --- /dev/null +++ b/include/godot_cpp/templates/sort_list.h @@ -0,0 +1,152 @@ +/**************************************************************************/ +/* sort_list.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include + +namespace godot { + +template > +class SortList { +public: + Comparator compare; + + void sort(Element *&r_head, Element *&r_tail) { + Element *sorted_until; + if (_is_sorted(r_head, r_tail, sorted_until)) { + return; + } + + // In case we're sorting only part of a larger list. + Element *head_prev = r_head->*prev; + r_head->*prev = nullptr; + Element *tail_next = r_tail->*next; + r_tail->*next = nullptr; + + // Sort unsorted section and merge. + Element *head2 = sorted_until->*next; + _split(sorted_until, head2); + _merge_sort(head2, r_tail); + _merge(r_head, sorted_until, head2, r_tail, r_head, r_tail); + + // Reconnect to larger list if needed. + if (head_prev) { + _connect(head_prev, r_head); + } + if (tail_next) { + _connect(r_tail, tail_next); + } + } + +private: + bool _is_sorted(Element *p_head, Element *p_tail, Element *&r_sorted_until) { + r_sorted_until = p_head; + while (r_sorted_until != p_tail) { + if (compare(r_sorted_until->*next->*value, r_sorted_until->*value)) { + return false; + } + + r_sorted_until = r_sorted_until->*next; + } + + return true; + } + + void _merge_sort(Element *&r_head, Element *&r_tail) { + if (r_head == r_tail) { + return; + } + + Element *tail1 = _get_mid(r_head); + Element *head2 = tail1->*next; + _split(tail1, head2); + + _merge_sort(r_head, tail1); + _merge_sort(head2, r_tail); + _merge(r_head, tail1, head2, r_tail, r_head, r_tail); + } + + void _merge( + Element *p_head1, Element *p_tail1, + Element *p_head2, Element *p_tail2, + Element *&r_head, Element *&r_tail) { + if (compare(p_head2->*value, p_head1->*value)) { + r_head = p_head2; + p_head2 = p_head2->*next; + } else { + r_head = p_head1; + p_head1 = p_head1->*next; + } + + Element *curr = r_head; + while (p_head1 && p_head2) { + if (compare(p_head2->*value, p_head1->*value)) { + _connect(curr, p_head2); + p_head2 = p_head2->*next; + } else { + _connect(curr, p_head1); + p_head1 = p_head1->*next; + } + curr = curr->*next; + } + + if (p_head1) { + _connect(curr, p_head1); + r_tail = p_tail1; + } else { + _connect(curr, p_head2); + r_tail = p_tail2; + } + } + + Element *_get_mid(Element *p_head) { + Element *end = p_head; + Element *mid = p_head; + while (end->*next && end->*next->*next) { + end = end->*next->*next; + mid = mid->*next; + } + + return mid; + } + + _FORCE_INLINE_ void _connect(Element *p_a, Element *p_b) { + p_a->*next = p_b; + p_b->*prev = p_a; + } + + _FORCE_INLINE_ void _split(Element *p_a, Element *p_b) { + p_a->*next = nullptr; + p_b->*prev = nullptr; + } +}; + +} // namespace godot diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 4995adb0e..3c496c3c4 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -50,6 +50,13 @@ void *Memory::alloc_static(size_t p_bytes, bool p_pad_align) { #endif } +void *Memory::alloc_static_zeroed(size_t p_bytes, bool p_pad_align) { + // TODO Could benefit from binding the upstream alloc_static_zeroed + void *mem = alloc_static(p_bytes, p_pad_align); + memset(mem, 0, p_bytes); + return mem; +} + void *Memory::realloc_static(void *p_memory, size_t p_bytes, bool p_pad_align) { if (p_memory == nullptr) { return alloc_static(p_bytes, p_pad_align);