From 1edfca295b87e37c675a7dac1dddd4e87739ea26 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Tue, 25 Feb 2025 09:29:22 -0600 Subject: [PATCH] Synchronize most shared template code with Godot 4.4 --- include/godot_cpp/templates/cowdata.hpp | 139 +++++++++----- include/godot_cpp/templates/hash_map.hpp | 127 +++++++++++-- include/godot_cpp/templates/hash_set.hpp | 46 +++-- include/godot_cpp/templates/hashfuncs.hpp | 167 ++++++++++++++--- include/godot_cpp/templates/list.hpp | 76 ++++++-- include/godot_cpp/templates/local_vector.hpp | 104 ++++++++--- include/godot_cpp/templates/pair.hpp | 9 + include/godot_cpp/templates/rb_map.hpp | 30 ++- include/godot_cpp/templates/rb_set.hpp | 10 +- include/godot_cpp/templates/safe_refcount.hpp | 173 ++++-------------- include/godot_cpp/templates/search_array.hpp | 10 +- include/godot_cpp/templates/self_list.hpp | 68 ++++++- include/godot_cpp/templates/sort_array.hpp | 78 ++++---- include/godot_cpp/templates/vector.hpp | 28 +-- include/godot_cpp/templates/vmap.hpp | 15 +- include/godot_cpp/templates/vset.hpp | 14 +- 16 files changed, 725 insertions(+), 369 deletions(-) diff --git a/include/godot_cpp/templates/cowdata.hpp b/include/godot_cpp/templates/cowdata.hpp index 68918f7ac..39ba5c8e7 100644 --- a/include/godot_cpp/templates/cowdata.hpp +++ b/include/godot_cpp/templates/cowdata.hpp @@ -37,8 +37,10 @@ #include #include +#include #include #include +#include namespace godot { @@ -166,13 +168,25 @@ class CowData { return *out; } - void _unref(void *p_data); + // Decrements the reference count. Deallocates the backing buffer if needed. + // After this function, _ptr is guaranteed to be NULL. + void _unref(); void _ref(const CowData *p_from); void _ref(const CowData &p_from); USize _copy_on_write(); + Error _realloc(Size p_alloc_size); public: void operator=(const CowData &p_from) { _ref(p_from); } + void operator=(CowData &&p_from) { + if (_ptr == p_from._ptr) { + return; + } + + _unref(); + _ptr = p_from._ptr; + p_from._ptr = nullptr; + } _FORCE_INLINE_ T *ptrw() { _copy_on_write(); @@ -221,19 +235,22 @@ class CowData { T *p = ptrw(); Size len = size(); for (Size i = p_index; i < len - 1; i++) { - p[i] = p[i + 1]; + p[i] = std::move(p[i + 1]); } resize(len - 1); } Error insert(Size p_pos, const T &p_val) { - ERR_FAIL_INDEX_V(p_pos, size() + 1, ERR_INVALID_PARAMETER); - resize(size() + 1); - for (Size i = (size() - 1); i > p_pos; i--) { - set(i, get(i - 1)); + Size new_size = size() + 1; + ERR_FAIL_INDEX_V(p_pos, new_size, ERR_INVALID_PARAMETER); + Error err = resize(new_size); + ERR_FAIL_COND_V(err, err); + T *p = ptrw(); + for (Size i = new_size - 1; i > p_pos; i--) { + p[i] = std::move(p[i - 1]); } - set(p_pos, p_val); + p[p_pos] = p_val; return OK; } @@ -243,35 +260,47 @@ class CowData { Size count(const T &p_val) const; _FORCE_INLINE_ CowData() {} - _FORCE_INLINE_ ~CowData(); - _FORCE_INLINE_ CowData(CowData &p_from) { _ref(p_from); }; + _FORCE_INLINE_ ~CowData() { _unref(); } + _FORCE_INLINE_ CowData(std::initializer_list p_init); + _FORCE_INLINE_ CowData(const CowData &p_from) { _ref(p_from); } + _FORCE_INLINE_ CowData(CowData &&p_from) { + _ptr = p_from._ptr; + p_from._ptr = nullptr; + } }; template -void CowData::_unref(void *p_data) { - if (!p_data) { +void CowData::_unref() { + if (!_ptr) { return; } SafeNumeric *refc = _get_refcount(); - if (refc->decrement() > 0) { - return; // still in use + // Data is still in use elsewhere. + _ptr = nullptr; + return; } - // clean up + // Clean up. + // First, invalidate our own reference. + // NOTE: It is required to do so immediately because it must not be observable outside of this + // function after refcount has already been reduced to 0. + // WARNING: It must be done before calling the destructors, because one of them may otherwise + // observe it through a reference to us. In this case, it may try to access the buffer, + // which is illegal after some of the elements in it have already been destructed, and + // may lead to a segmentation fault. + USize current_size = *_get_size(); + T *prev_ptr = _ptr; + _ptr = nullptr; if constexpr (!std::is_trivially_destructible_v) { - USize *count = _get_size(); - T *data = (T *)(count + 1); - - for (USize i = 0; i < *count; ++i) { - // call destructors - data[i].~T(); + for (USize i = 0; i < current_size; ++i) { + prev_ptr[i].~T(); } } // free mem - Memory::free_static(((uint8_t *)p_data) - DATA_OFFSET, false); + Memory::free_static((uint8_t *)prev_ptr - DATA_OFFSET, false); } template @@ -306,7 +335,7 @@ typename CowData::USize CowData::_copy_on_write() { } } - _unref(_ptr); + _unref(); _ptr = _data_ptr; rc = 1; @@ -326,14 +355,13 @@ Error CowData::resize(Size p_size) { } if (p_size == 0) { - // wants to clean up - _unref(_ptr); - _ptr = nullptr; + // Wants to clean up. + _unref(); // Resets _ptr to nullptr. return OK; } // possibly changing size, copy on write - USize rc = _copy_on_write(); + _copy_on_write(); USize current_alloc_size = _get_alloc_size(current_size); USize alloc_size; @@ -354,16 +382,12 @@ Error CowData::resize(Size p_size) { *(_size_ptr) = 0; //size, currently none _ptr = _data_ptr; - } else { - uint8_t *mem_new = (uint8_t *)Memory::realloc_static(((uint8_t *)_ptr) - DATA_OFFSET, alloc_size + DATA_OFFSET, false); - ERR_FAIL_NULL_V(mem_new, ERR_OUT_OF_MEMORY); - - SafeNumeric *_refc_ptr = _get_refcount_ptr(mem_new); - T *_data_ptr = _get_data_ptr(mem_new); - new (_refc_ptr) SafeNumeric(rc); //refcount - - _ptr = _data_ptr; + } else { + const Error error = _realloc(alloc_size); + if (error) { + return error; + } } } @@ -389,15 +413,10 @@ Error CowData::resize(Size p_size) { } if (alloc_size != current_alloc_size) { - uint8_t *mem_new = (uint8_t *)Memory::realloc_static(((uint8_t *)_ptr) - DATA_OFFSET, alloc_size + DATA_OFFSET, false); - ERR_FAIL_NULL_V(mem_new, ERR_OUT_OF_MEMORY); - - SafeNumeric *_refc_ptr = _get_refcount_ptr(mem_new); - T *_data_ptr = _get_data_ptr(mem_new); - - new (_refc_ptr) SafeNumeric(rc); //refcount - - _ptr = _data_ptr; + const Error error = _realloc(alloc_size); + if (error) { + return error; + } } *_get_size() = p_size; @@ -406,6 +425,21 @@ Error CowData::resize(Size p_size) { return OK; } +template +Error CowData::_realloc(Size p_alloc_size) { + uint8_t *mem_new = (uint8_t *)Memory::realloc_static(((uint8_t *)_ptr) - DATA_OFFSET, p_alloc_size + DATA_OFFSET, false); + ERR_FAIL_NULL_V(mem_new, ERR_OUT_OF_MEMORY); + + SafeNumeric *_refc_ptr = _get_refcount_ptr(mem_new); + T *_data_ptr = _get_data_ptr(mem_new); + + // If we realloc, we're guaranteed to be the only reference. + new (_refc_ptr) SafeNumeric(1); + _ptr = _data_ptr; + + return OK; +} + template typename CowData::Size CowData::find(const T &p_val, Size p_from) const { Size ret = -1; @@ -465,11 +499,10 @@ void CowData::_ref(const CowData &p_from) { return; // self assign, do nothing. } - _unref(_ptr); - _ptr = nullptr; + _unref(); // Resets _ptr to nullptr. if (!p_from._ptr) { - return; // nothing to do + return; //nothing to do } if (p_from._get_refcount()->conditional_increment() > 0) { // could reference @@ -478,8 +511,16 @@ void CowData::_ref(const CowData &p_from) { } template -CowData::~CowData() { - _unref(_ptr); +CowData::CowData(std::initializer_list p_init) { + Error err = resize(p_init.size()); + if (err != OK) { + return; + } + + Size i = 0; + for (const T &element : p_init) { + set(i++, element); + } } #if defined(__GNUC__) && !defined(__clang__) diff --git a/include/godot_cpp/templates/hash_map.hpp b/include/godot_cpp/templates/hash_map.hpp index 67dfb4e77..a0a8cadab 100644 --- a/include/godot_cpp/templates/hash_map.hpp +++ b/include/godot_cpp/templates/hash_map.hpp @@ -61,15 +61,17 @@ 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 { public: - const uint32_t MIN_CAPACITY_INDEX = 2; // Use a prime. - const float MAX_OCCUPANCY = 0.75; - const uint32_t EMPTY_HASH = 0; + 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; private: Allocator element_alloc; @@ -91,19 +93,20 @@ class HashMap { return hash; } - _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; + 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); } bool _lookup_pos(const TKey &p_key, uint32_t &r_pos) const { - if (elements == nullptr) { + if (elements == nullptr || num_elements == 0) { return false; // Failed lookups, no elements } - uint32_t capacity = hash_table_size_primes[capacity_index]; + 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 = hash % capacity; + uint32_t pos = fastmod(hash, capacity_inv, capacity); uint32_t distance = 0; while (true) { @@ -111,7 +114,7 @@ class HashMap { return false; } - if (distance > _get_probe_length(pos, hashes[pos], capacity)) { + if (distance > _get_probe_length(pos, hashes[pos], capacity, capacity_inv)) { return false; } @@ -120,17 +123,18 @@ class HashMap { return true; } - pos = (pos + 1) % capacity; + pos = fastmod((pos + 1), capacity_inv, capacity); distance++; } } void _insert_with_hash(uint32_t p_hash, HashMapElement *p_value) { - uint32_t capacity = hash_table_size_primes[capacity_index]; + 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 = p_hash; HashMapElement *value = p_value; uint32_t distance = 0; - uint32_t pos = hash % capacity; + uint32_t pos = fastmod(hash, capacity_inv, capacity); while (true) { if (hashes[pos] == EMPTY_HASH) { @@ -143,14 +147,14 @@ class HashMap { } // 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); + uint32_t existing_probe_len = _get_probe_length(pos, hashes[pos], capacity, capacity_inv); if (existing_probe_len < distance) { SWAP(hash, hashes[pos]); SWAP(value, elements[pos]); distance = existing_probe_len; } - pos = (pos + 1) % capacity; + pos = fastmod((pos + 1), capacity_inv, capacity); distance++; } } @@ -250,7 +254,7 @@ class HashMap { } void clear() { - if (elements == nullptr) { + if (elements == nullptr || num_elements == 0) { return; } uint32_t capacity = hash_table_size_primes[capacity_index]; @@ -269,6 +273,47 @@ class HashMap { num_elements = 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; + } + } + TValue &get(const TKey &p_key) { uint32_t pos = 0; bool exists = _lookup_pos(p_key, pos); @@ -316,13 +361,14 @@ class HashMap { return false; } - 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) { + 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 = (pos + 1) % capacity; + next_pos = fastmod((pos + 1), capacity_inv, capacity); } hashes[pos] = EMPTY_HASH; @@ -350,6 +396,40 @@ class HashMap { 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) { + 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--; + + // 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); + + return true; + } + // 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) { @@ -560,6 +640,13 @@ class HashMap { capacity_index = MIN_CAPACITY_INDEX; } + HashMap(std::initializer_list> p_init) { + reserve(p_init.size()); + for (const KeyValue &E : p_init) { + insert(E.key, E.value); + } + } + uint32_t debug_get_hash(uint32_t p_index) { if (num_elements == 0) { return 0; diff --git a/include/godot_cpp/templates/hash_set.hpp b/include/godot_cpp/templates/hash_set.hpp index 67ba443aa..a4eb68546 100644 --- a/include/godot_cpp/templates/hash_set.hpp +++ b/include/godot_cpp/templates/hash_set.hpp @@ -75,19 +75,20 @@ class HashSet { return hash; } - _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; + 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); } bool _lookup_pos(const TKey &p_key, uint32_t &r_pos) const { - if (keys == nullptr) { + if (keys == nullptr || num_elements == 0) { return false; // Failed lookups, no elements } - uint32_t capacity = hash_table_size_primes[capacity_index]; + 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 = hash % capacity; + uint32_t pos = fastmod(hash, capacity_inv, capacity); uint32_t distance = 0; while (true) { @@ -95,7 +96,7 @@ class HashSet { return false; } - if (distance > _get_probe_length(pos, hashes[pos], capacity)) { + if (distance > _get_probe_length(pos, hashes[pos], capacity, capacity_inv)) { return false; } @@ -104,17 +105,18 @@ class HashSet { return true; } - pos = (pos + 1) % capacity; + pos = fastmod(pos + 1, capacity_inv, capacity); distance++; } } uint32_t _insert_with_hash(uint32_t p_hash, uint32_t p_index) { - uint32_t capacity = hash_table_size_primes[capacity_index]; + 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 = p_hash; uint32_t index = p_index; uint32_t distance = 0; - uint32_t pos = hash % capacity; + uint32_t pos = fastmod(hash, capacity_inv, capacity); while (true) { if (hashes[pos] == EMPTY_HASH) { @@ -125,7 +127,7 @@ class HashSet { } // 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); + uint32_t existing_probe_len = _get_probe_length(pos, hashes[pos], capacity, capacity_inv); if (existing_probe_len < distance) { key_to_hash[index] = pos; SWAP(hash, hashes[pos]); @@ -133,7 +135,7 @@ class HashSet { distance = existing_probe_len; } - pos = (pos + 1) % capacity; + pos = fastmod(pos + 1, capacity_inv, capacity); distance++; } } @@ -236,7 +238,7 @@ class HashSet { } void clear() { - if (keys == nullptr) { + if (keys == nullptr || num_elements == 0) { return; } uint32_t capacity = hash_table_size_primes[capacity_index]; @@ -264,11 +266,12 @@ class HashSet { } uint32_t key_pos = pos; - pos = key_to_hash[pos]; // make hash pos + pos = key_to_hash[pos]; //make hash pos - 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) { + 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) { uint32_t kpos = hash_to_key[pos]; uint32_t kpos_next = hash_to_key[next_pos]; SWAP(key_to_hash[kpos], key_to_hash[kpos_next]); @@ -276,7 +279,7 @@ class HashSet { SWAP(hash_to_key[next_pos], hash_to_key[pos]); pos = next_pos; - next_pos = (pos + 1) % capacity; + next_pos = fastmod(pos + 1, capacity_inv, capacity); } hashes[pos] = EMPTY_HASH; @@ -443,6 +446,13 @@ class HashSet { capacity_index = MIN_CAPACITY_INDEX; } + HashSet(std::initializer_list p_init) { + reserve(p_init.size()); + for (const TKey &E : p_init) { + insert(E); + } + } + void reset() { clear(); diff --git a/include/godot_cpp/templates/hashfuncs.hpp b/include/godot_cpp/templates/hashfuncs.hpp index 3fcedb693..566b8c71a 100644 --- a/include/godot_cpp/templates/hashfuncs.hpp +++ b/include/godot_cpp/templates/hashfuncs.hpp @@ -66,10 +66,11 @@ namespace godot { static _FORCE_INLINE_ uint32_t hash_djb2(const char *p_cstr) { const unsigned char *chr = (const unsigned char *)p_cstr; uint32_t hash = 5381; - uint32_t c; + uint32_t c = *chr++; - while ((c = *chr++)) { + while (c) { hash = ((hash << 5) + hash) ^ c; /* hash * 33 ^ c */ + c = *chr++; } return hash; @@ -107,6 +108,16 @@ static _FORCE_INLINE_ uint32_t hash_one_uint64(const uint64_t p_int) { return uint32_t(v); } +static _FORCE_INLINE_ uint64_t hash64_murmur3_64(uint64_t key, uint64_t seed) { + key ^= seed; + key ^= key >> 33; + key *= 0xff51afd7ed558ccd; + key ^= key >> 33; + key *= 0xc4ceb9fe1a85ec53; + key ^= key >> 33; + return key; +} + #define HASH_MURMUR3_SEED 0x7F07C65 // Murmurhash3 32-bit version. // All MurmurHash versions are public domain software, and the author disclaims all copyright to their code. @@ -227,7 +238,7 @@ static _FORCE_INLINE_ uint32_t hash_murmur3_buffer(const void *key, int length, k1 = hash_rotl32(k1, 15); k1 *= c2; h1 ^= k1; - } + }; // Finalize with additional bit mixing. h1 ^= length; @@ -310,40 +321,41 @@ struct HashMapHasherDefault { static _FORCE_INLINE_ uint32_t hash(const String &p_string) { return p_string.hash(); } static _FORCE_INLINE_ uint32_t hash(const char *p_cstr) { return hash_djb2(p_cstr); } - static _FORCE_INLINE_ uint32_t hash(const wchar_t p_wchar) { return hash_fmix32(p_wchar); } - static _FORCE_INLINE_ uint32_t hash(const char16_t p_uchar) { return hash_fmix32(p_uchar); } - static _FORCE_INLINE_ uint32_t hash(const char32_t p_uchar) { return hash_fmix32(p_uchar); } + static _FORCE_INLINE_ uint32_t hash(const wchar_t p_wchar) { return hash_fmix32(uint32_t(p_wchar)); } + static _FORCE_INLINE_ uint32_t hash(const char16_t p_uchar) { return hash_fmix32(uint32_t(p_uchar)); } + static _FORCE_INLINE_ uint32_t hash(const char32_t p_uchar) { return hash_fmix32(uint32_t(p_uchar)); } static _FORCE_INLINE_ uint32_t hash(const RID &p_rid) { return hash_one_uint64(p_rid.get_id()); } + static _FORCE_INLINE_ uint32_t hash(const CharString &p_char_string) { return hash_djb2(p_char_string.get_data()); } static _FORCE_INLINE_ uint32_t hash(const StringName &p_string_name) { return p_string_name.hash(); } static _FORCE_INLINE_ uint32_t hash(const NodePath &p_path) { return p_path.hash(); } static _FORCE_INLINE_ uint32_t hash(const ObjectID &p_id) { return hash_one_uint64(p_id); } static _FORCE_INLINE_ uint32_t hash(const uint64_t p_int) { return hash_one_uint64(p_int); } - static _FORCE_INLINE_ uint32_t hash(const int64_t p_int) { return hash_one_uint64(p_int); } + static _FORCE_INLINE_ uint32_t hash(const int64_t p_int) { return hash_one_uint64(uint64_t(p_int)); } static _FORCE_INLINE_ uint32_t hash(const float p_float) { return hash_murmur3_one_float(p_float); } static _FORCE_INLINE_ uint32_t hash(const double p_double) { return hash_murmur3_one_double(p_double); } static _FORCE_INLINE_ uint32_t hash(const uint32_t p_int) { return hash_fmix32(p_int); } - static _FORCE_INLINE_ uint32_t hash(const int32_t p_int) { return hash_fmix32(p_int); } - static _FORCE_INLINE_ uint32_t hash(const uint16_t p_int) { return hash_fmix32(p_int); } - static _FORCE_INLINE_ uint32_t hash(const int16_t p_int) { return hash_fmix32(p_int); } - static _FORCE_INLINE_ uint32_t hash(const uint8_t p_int) { return hash_fmix32(p_int); } - static _FORCE_INLINE_ uint32_t hash(const int8_t p_int) { return hash_fmix32(p_int); } + static _FORCE_INLINE_ uint32_t hash(const int32_t p_int) { return hash_fmix32(uint32_t(p_int)); } + static _FORCE_INLINE_ uint32_t hash(const uint16_t p_int) { return hash_fmix32(uint32_t(p_int)); } + static _FORCE_INLINE_ uint32_t hash(const int16_t p_int) { return hash_fmix32(uint32_t(p_int)); } + static _FORCE_INLINE_ uint32_t hash(const uint8_t p_int) { return hash_fmix32(uint32_t(p_int)); } + static _FORCE_INLINE_ uint32_t hash(const int8_t p_int) { return hash_fmix32(uint32_t(p_int)); } static _FORCE_INLINE_ uint32_t hash(const Vector2i &p_vec) { - uint32_t h = hash_murmur3_one_32(p_vec.x); - h = hash_murmur3_one_32(p_vec.y, h); + uint32_t h = hash_murmur3_one_32(uint32_t(p_vec.x)); + h = hash_murmur3_one_32(uint32_t(p_vec.y), h); return hash_fmix32(h); } static _FORCE_INLINE_ uint32_t hash(const Vector3i &p_vec) { - uint32_t h = hash_murmur3_one_32(p_vec.x); - h = hash_murmur3_one_32(p_vec.y, h); - h = hash_murmur3_one_32(p_vec.z, h); + uint32_t h = hash_murmur3_one_32(uint32_t(p_vec.x)); + h = hash_murmur3_one_32(uint32_t(p_vec.y), h); + h = hash_murmur3_one_32(uint32_t(p_vec.z), h); return hash_fmix32(h); } static _FORCE_INLINE_ uint32_t hash(const Vector4i &p_vec) { - uint32_t h = hash_murmur3_one_32(p_vec.x); - h = hash_murmur3_one_32(p_vec.y, h); - h = hash_murmur3_one_32(p_vec.z, h); - h = hash_murmur3_one_32(p_vec.w, h); + uint32_t h = hash_murmur3_one_32(uint32_t(p_vec.x)); + h = hash_murmur3_one_32(uint32_t(p_vec.y), h); + h = hash_murmur3_one_32(uint32_t(p_vec.z), h); + h = hash_murmur3_one_32(uint32_t(p_vec.w), h); return hash_fmix32(h); } static _FORCE_INLINE_ uint32_t hash(const Vector2 &p_vec) { @@ -365,10 +377,10 @@ struct HashMapHasherDefault { return hash_fmix32(h); } static _FORCE_INLINE_ uint32_t hash(const Rect2i &p_rect) { - uint32_t h = hash_murmur3_one_32(p_rect.position.x); - h = hash_murmur3_one_32(p_rect.position.y, h); - h = hash_murmur3_one_32(p_rect.size.x, h); - h = hash_murmur3_one_32(p_rect.size.y, h); + uint32_t h = hash_murmur3_one_32(uint32_t(p_rect.position.x)); + h = hash_murmur3_one_32(uint32_t(p_rect.position.y), h); + h = hash_murmur3_one_32(uint32_t(p_rect.size.x), h); + h = hash_murmur3_one_32(uint32_t(p_rect.size.y), h); return hash_fmix32(h); } static _FORCE_INLINE_ uint32_t hash(const Rect2 &p_rect) { @@ -389,6 +401,19 @@ struct HashMapHasherDefault { } }; +struct HashHasher { + static _FORCE_INLINE_ uint32_t hash(const int32_t hash) { return hash; } + static _FORCE_INLINE_ uint32_t hash(const uint32_t hash) { return hash; } + static _FORCE_INLINE_ uint64_t hash(const int64_t hash) { return hash; } + static _FORCE_INLINE_ uint64_t hash(const uint64_t hash) { return hash; } +}; + +// TODO: Fold this into HashMapHasherDefault once C++20 concepts are allowed +template +struct HashableHasher { + static _FORCE_INLINE_ uint32_t hash(const T &hashable) { return hashable.hash(); } +}; + template struct HashMapComparatorDefault { static bool compare(const T &p_lhs, const T &p_rhs) { @@ -410,6 +435,13 @@ struct HashMapComparatorDefault { } }; +template <> +struct HashMapComparatorDefault { + static bool compare(const Color &p_lhs, const Color &p_rhs) { + return ((p_lhs.r == p_rhs.r) || (Math::is_nan(p_lhs.r) && Math::is_nan(p_rhs.r))) && ((p_lhs.g == p_rhs.g) || (Math::is_nan(p_lhs.g) && Math::is_nan(p_rhs.g))) && ((p_lhs.b == p_rhs.b) || (Math::is_nan(p_lhs.b) && Math::is_nan(p_rhs.b))) && ((p_lhs.a == p_rhs.a) || (Math::is_nan(p_lhs.a) && Math::is_nan(p_rhs.a))); + } +}; + template <> struct HashMapComparatorDefault { static bool compare(const Vector2 &p_lhs, const Vector2 &p_rhs) { @@ -424,9 +456,90 @@ struct HashMapComparatorDefault { } }; +template <> +struct HashMapComparatorDefault { + static bool compare(const Vector4 &p_lhs, const Vector4 &p_rhs) { + return ((p_lhs.x == p_rhs.x) || (Math::is_nan(p_lhs.x) && Math::is_nan(p_rhs.x))) && ((p_lhs.y == p_rhs.y) || (Math::is_nan(p_lhs.y) && Math::is_nan(p_rhs.y))) && ((p_lhs.z == p_rhs.z) || (Math::is_nan(p_lhs.z) && Math::is_nan(p_rhs.z))) && ((p_lhs.w == p_rhs.w) || (Math::is_nan(p_lhs.w) && Math::is_nan(p_rhs.w))); + } +}; + +template <> +struct HashMapComparatorDefault { + static bool compare(const Rect2 &p_lhs, const Rect2 &p_rhs) { + return HashMapComparatorDefault().compare(p_lhs.position, p_rhs.position) && HashMapComparatorDefault().compare(p_lhs.size, p_rhs.size); + } +}; + +template <> +struct HashMapComparatorDefault { + static bool compare(const AABB &p_lhs, const AABB &p_rhs) { + return HashMapComparatorDefault().compare(p_lhs.position, p_rhs.position) && HashMapComparatorDefault().compare(p_lhs.size, p_rhs.size); + } +}; + +template <> +struct HashMapComparatorDefault { + static bool compare(const Plane &p_lhs, const Plane &p_rhs) { + return HashMapComparatorDefault().compare(p_lhs.normal, p_rhs.normal) && ((p_lhs.d == p_rhs.d) || (Math::is_nan(p_lhs.d) && Math::is_nan(p_rhs.d))); + } +}; + +template <> +struct HashMapComparatorDefault { + static bool compare(const Transform2D &p_lhs, const Transform2D &p_rhs) { + for (int i = 0; i < 3; ++i) { + if (!HashMapComparatorDefault().compare(p_lhs.columns[i], p_rhs.columns[i])) { + return false; + } + } + + return true; + } +}; + +template <> +struct HashMapComparatorDefault { + static bool compare(const Basis &p_lhs, const Basis &p_rhs) { + for (int i = 0; i < 3; ++i) { + if (!HashMapComparatorDefault().compare(p_lhs.rows[i], p_rhs.rows[i])) { + return false; + } + } + + return true; + } +}; + +template <> +struct HashMapComparatorDefault { + static bool compare(const Transform3D &p_lhs, const Transform3D &p_rhs) { + return HashMapComparatorDefault().compare(p_lhs.basis, p_rhs.basis) && HashMapComparatorDefault().compare(p_lhs.origin, p_rhs.origin); + } +}; + +template <> +struct HashMapComparatorDefault { + static bool compare(const Projection &p_lhs, const Projection &p_rhs) { + for (int i = 0; i < 4; ++i) { + if (!HashMapComparatorDefault().compare(p_lhs.columns[i], p_rhs.columns[i])) { + return false; + } + } + + return true; + } +}; + +template <> +struct HashMapComparatorDefault { + static bool compare(const Quaternion &p_lhs, const Quaternion &p_rhs) { + return ((p_lhs.x == p_rhs.x) || (Math::is_nan(p_lhs.x) && Math::is_nan(p_rhs.x))) && ((p_lhs.y == p_rhs.y) || (Math::is_nan(p_lhs.y) && Math::is_nan(p_rhs.y))) && ((p_lhs.z == p_rhs.z) || (Math::is_nan(p_lhs.z) && Math::is_nan(p_rhs.z))) && ((p_lhs.w == p_rhs.w) || (Math::is_nan(p_lhs.w) && Math::is_nan(p_rhs.w))); + } +}; + constexpr uint32_t HASH_TABLE_SIZE_MAX = 29; -const uint32_t hash_table_size_primes[HASH_TABLE_SIZE_MAX] = { +inline constexpr uint32_t hash_table_size_primes[HASH_TABLE_SIZE_MAX] = { 5, 13, 23, @@ -459,7 +572,7 @@ const uint32_t hash_table_size_primes[HASH_TABLE_SIZE_MAX] = { }; // Computed with elem_i = UINT64_C (0 x FFFFFFFF FFFFFFFF ) / d_i + 1, where d_i is the i-th element of the above array. -const uint64_t hash_table_size_primes_inv[HASH_TABLE_SIZE_MAX] = { +inline constexpr uint64_t hash_table_size_primes_inv[HASH_TABLE_SIZE_MAX] = { 3689348814741910324, 1418980313362273202, 802032351030850071, diff --git a/include/godot_cpp/templates/list.hpp b/include/godot_cpp/templates/list.hpp index bbc818c7d..9291f5e40 100644 --- a/include/godot_cpp/templates/list.hpp +++ b/include/godot_cpp/templates/list.hpp @@ -34,6 +34,8 @@ #include #include +#include + /** * Generic Templatized Linked List Implementation. * The implementation differs from the STL one because @@ -133,6 +135,8 @@ class List { data->erase(this); } + void transfer_to_back(List *p_dst_list); + _FORCE_INLINE_ Element() {} }; @@ -223,7 +227,7 @@ class List { Element *last = nullptr; int size_cache = 0; - bool erase(const Element *p_I) { + bool erase(Element *p_I) { ERR_FAIL_NULL_V(p_I, false); ERR_FAIL_COND_V(p_I->data != this, false); @@ -243,7 +247,7 @@ class List { p_I->next_ptr->prev_ptr = p_I->prev_ptr; } - memdelete_allocator(const_cast(p_I)); + memdelete_allocator(p_I); size_cache--; return true; @@ -429,7 +433,7 @@ class List { /** * erase an element in the list, by iterator pointing to it. Return true if it was found/erased. */ - bool erase(const Element *p_I) { + bool erase(Element *p_I) { if (_data && p_I) { bool ret = _data->erase(p_I); @@ -521,10 +525,14 @@ class List { it = it->next(); } } + void operator=(List &&p_list) { + if (unlikely(this == &p_list)) { + return; + } - // Index operator, kept for compatibility. - _FORCE_INLINE_ T &operator[](int p_index) { - return get(p_index); + clear(); + _data = p_list._data; + p_list._data = nullptr; } // Random access to elements, use with care, @@ -542,11 +550,6 @@ class List { return I->get(); } - // Index operator, kept for compatibility. - _FORCE_INLINE_ const T &operator[](int p_index) const { - return get(p_index); - } - // Random access to elements, use with care, // do not use for iteration. const T &get(int p_index) const { @@ -720,8 +723,8 @@ class List { template void sort_custom() { - // this version uses auxiliary memory for speed. - // if you don't want to use auxiliary memory, use the in_place version + //this version uses auxiliary memory for speed. + //if you don't want to use auxiliary memory, use the in_place version int s = size(); if (s < 2) { @@ -769,9 +772,19 @@ class List { it = it->next(); } } + List(List &&p_list) { + _data = p_list._data; + p_list._data = nullptr; + } List() {} + List(std::initializer_list p_init) { + for (const T &E : p_init) { + push_back(E); + } + } + ~List() { clear(); if (_data) { @@ -781,4 +794,41 @@ class List { } }; +template +void List::Element::transfer_to_back(List *p_dst_list) { + // Detach from current. + + if (data->first == this) { + data->first = data->first->next_ptr; + } + if (data->last == this) { + data->last = data->last->prev_ptr; + } + if (prev_ptr) { + prev_ptr->next_ptr = next_ptr; + } + if (next_ptr) { + next_ptr->prev_ptr = prev_ptr; + } + data->size_cache--; + + // Attach to the back of the new one. + + if (!p_dst_list->_data) { + p_dst_list->_data = memnew_allocator(_Data, A); + p_dst_list->_data->first = this; + p_dst_list->_data->last = nullptr; + p_dst_list->_data->size_cache = 0; + prev_ptr = nullptr; + } else { + p_dst_list->_data->last->next_ptr = this; + prev_ptr = p_dst_list->_data->last; + } + p_dst_list->_data->last = this; + next_ptr = nullptr; + + data = p_dst_list->_data; + p_dst_list->_data->size_cache++; +} + } // namespace godot diff --git a/include/godot_cpp/templates/local_vector.hpp b/include/godot_cpp/templates/local_vector.hpp index 7b684874a..0ef322fba 100644 --- a/include/godot_cpp/templates/local_vector.hpp +++ b/include/godot_cpp/templates/local_vector.hpp @@ -58,21 +58,18 @@ class LocalVector { return data; } + // Must take a copy instead of a reference (see GH-31736). _FORCE_INLINE_ void push_back(T p_elem) { if (unlikely(count == capacity)) { - if (capacity == 0) { - capacity = 1; - } else { - capacity <<= 1; - } + capacity = tight ? (capacity + 1) : MAX((U)1, capacity << 1); data = (T *)memrealloc(data, capacity * sizeof(T)); CRASH_COND_MSG(!data, "Out of memory"); } - if constexpr (!std::is_trivially_constructible::value && !force_trivial) { + if constexpr (!std::is_trivially_constructible_v && !force_trivial) { memnew_placement(&data[count++], T(p_elem)); } else { - data[count++] = p_elem; + data[count++] = std::move(p_elem); } } @@ -80,31 +77,49 @@ class LocalVector { ERR_FAIL_UNSIGNED_INDEX(p_index, count); count--; for (U i = p_index; i < count; i++) { - data[i] = data[i + 1]; + data[i] = std::move(data[i + 1]); } - if constexpr (!std::is_trivially_destructible::value && !force_trivial) { + if constexpr (!std::is_trivially_destructible_v && !force_trivial) { data[count].~T(); } } /// Removes the item copying the last value into the position of the one to - /// remove. It's generally faster than `remove`. + /// remove. It's generally faster than `remove_at`. void remove_at_unordered(U p_index) { ERR_FAIL_INDEX(p_index, count); count--; if (count > p_index) { - data[p_index] = data[count]; + data[p_index] = std::move(data[count]); } - if constexpr (!std::is_trivially_destructible::value && !force_trivial) { + if constexpr (!std::is_trivially_destructible_v && !force_trivial) { data[count].~T(); } } - void erase(const T &p_val) { + _FORCE_INLINE_ bool erase(const T &p_val) { int64_t idx = find(p_val); if (idx >= 0) { remove_at(idx); + return true; } + return false; + } + + U erase_multiple_unordered(const T &p_val) { + U from = 0; + U occurrences = 0; + while (true) { + int64_t idx = find(p_val, from); + + if (idx == -1) { + break; + } + remove_at_unordered(idx); + from = idx; + occurrences++; + } + return occurrences; } void invert() { @@ -136,7 +151,7 @@ class LocalVector { _FORCE_INLINE_ U size() const { return count; } void resize(U p_size) { if (p_size < count) { - if constexpr (!std::is_trivially_destructible::value && !force_trivial) { + if constexpr (!std::is_trivially_destructible_v && !force_trivial) { for (U i = p_size; i < count; i++) { data[i].~T(); } @@ -144,16 +159,11 @@ class LocalVector { count = p_size; } else if (p_size > count) { if (unlikely(p_size > capacity)) { - if (capacity == 0) { - capacity = 1; - } - while (capacity < p_size) { - capacity <<= 1; - } + capacity = tight ? p_size : nearest_power_of_2_templated(p_size); data = (T *)memrealloc(data, capacity * sizeof(T)); CRASH_COND_MSG(!data, "Out of memory"); } - if constexpr (!std::is_trivially_constructible::value && !force_trivial) { + if constexpr (!std::is_trivially_constructible_v && !force_trivial) { for (U i = count; i < p_size; i++) { memnew_placement(&data[i], T); } @@ -237,13 +247,13 @@ class LocalVector { void insert(U p_pos, T p_val) { ERR_FAIL_UNSIGNED_INDEX(p_pos, count + 1); if (p_pos == count) { - push_back(p_val); + push_back(std::move(p_val)); } else { resize(count + 1); for (U i = count - 1; i > p_pos; i--) { - data[i] = data[i - 1]; + data[i] = std::move(data[i - 1]); } - data[p_pos] = p_val; + data[p_pos] = std::move(p_val); } } @@ -287,9 +297,17 @@ class LocalVector { operator Vector() const { Vector ret; - ret.resize(size()); + ret.resize(count); T *w = ret.ptrw(); - memcpy(w, data, sizeof(T) * count); + if (w) { + if constexpr (std::is_trivially_copyable_v) { + memcpy(w, data, sizeof(T) * count); + } else { + for (U i = 0; i < count; i++) { + w[i] = data[i]; + } + } + } return ret; } @@ -297,7 +315,9 @@ class LocalVector { Vector ret; ret.resize(count * sizeof(T)); uint8_t *w = ret.ptrw(); - memcpy(w, data, sizeof(T) * count); + if (w) { + memcpy(w, data, sizeof(T) * count); + } return ret; } @@ -314,6 +334,16 @@ class LocalVector { data[i] = p_from.data[i]; } } + _FORCE_INLINE_ LocalVector(LocalVector &&p_from) { + data = p_from.data; + count = p_from.count; + capacity = p_from.capacity; + + p_from.data = nullptr; + p_from.count = 0; + p_from.capacity = 0; + } + inline void operator=(const LocalVector &p_from) { resize(p_from.size()); for (U i = 0; i < p_from.count; i++) { @@ -326,6 +356,26 @@ class LocalVector { data[i] = p_from[i]; } } + inline void operator=(LocalVector &&p_from) { + if (unlikely(this == &p_from)) { + return; + } + reset(); + + data = p_from.data; + count = p_from.count; + capacity = p_from.capacity; + + p_from.data = nullptr; + p_from.count = 0; + p_from.capacity = 0; + } + inline void operator=(Vector &&p_from) { + resize(p_from.size()); + for (U i = 0; i < count; i++) { + data[i] = std::move(p_from[i]); + } + } _FORCE_INLINE_ ~LocalVector() { if (data) { diff --git a/include/godot_cpp/templates/pair.hpp b/include/godot_cpp/templates/pair.hpp index 5c064c1cf..423a7099e 100644 --- a/include/godot_cpp/templates/pair.hpp +++ b/include/godot_cpp/templates/pair.hpp @@ -68,6 +68,15 @@ struct PairSort { } }; +template +struct PairHash { + static uint32_t hash(const Pair &P) { + uint64_t h1 = HashMapHasherDefault::hash(P.first); + uint64_t h2 = HashMapHasherDefault::hash(P.second); + return hash_one_uint64((h1 << 32) | h2); + } +}; + template struct KeyValue { const K key; diff --git a/include/godot_cpp/templates/rb_map.hpp b/include/godot_cpp/templates/rb_map.hpp index 0ddb19839..c0dc4bf33 100644 --- a/include/godot_cpp/templates/rb_map.hpp +++ b/include/godot_cpp/templates/rb_map.hpp @@ -34,6 +34,8 @@ #include #include +#include + namespace godot { // based on the very nice implementation of rb-trees by: @@ -97,6 +99,8 @@ class RBMap { typedef KeyValue ValueType; struct Iterator { + friend class RBMap; + _FORCE_INLINE_ KeyValue &operator*() const { return E->key_value(); } @@ -110,11 +114,16 @@ class RBMap { 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; } + _FORCE_INLINE_ bool operator==(const Iterator &p_it) const { return E == p_it.E; } + _FORCE_INLINE_ bool operator!=(const Iterator &p_it) const { return E != p_it.E; } explicit operator bool() const { return E != nullptr; } + + Iterator &operator=(const Iterator &p_it) { + E = p_it.E; + return *this; + } Iterator(Element *p_E) { E = p_E; } Iterator() {} Iterator(const Iterator &p_it) { E = p_it.E; } @@ -137,11 +146,16 @@ class RBMap { return *this; } - _FORCE_INLINE_ bool operator==(const ConstIterator &b) const { return E == b.E; } - _FORCE_INLINE_ bool operator!=(const ConstIterator &b) const { return E != b.E; } + _FORCE_INLINE_ bool operator==(const ConstIterator &p_it) const { return E == p_it.E; } + _FORCE_INLINE_ bool operator!=(const ConstIterator &p_it) const { return E != p_it.E; } explicit operator bool() const { return E != nullptr; } + + ConstIterator &operator=(const ConstIterator &p_it) { + E = p_it.E; + return *this; + } ConstIterator(const Element *p_E) { E = p_E; } ConstIterator() {} ConstIterator(const ConstIterator &p_it) { E = p_it.E; } @@ -418,7 +432,7 @@ class RBMap { new_node->right = _data._nil; new_node->left = _data._nil; - // new_node->data=_data; + //new_node->data=_data; if (new_parent == _data._root || less(p_key, new_parent->_data.key)) { new_parent->left = new_node; @@ -752,6 +766,12 @@ class RBMap { _copy_from(p_map); } + RBMap(std::initializer_list> p_init) { + for (const KeyValue &E : p_init) { + insert(E.key, E.value); + } + } + _FORCE_INLINE_ RBMap() {} ~RBMap() { diff --git a/include/godot_cpp/templates/rb_set.hpp b/include/godot_cpp/templates/rb_set.hpp index f826896f7..612be9115 100644 --- a/include/godot_cpp/templates/rb_set.hpp +++ b/include/godot_cpp/templates/rb_set.hpp @@ -32,6 +32,8 @@ #include +#include + // based on the very nice implementation of rb-trees by: // https://web.archive.org/web/20120507164830/https://web.mit.edu/~emin/www/source_code/red_black_tree/index.html @@ -398,7 +400,7 @@ class RBSet { new_node->right = _data._nil; new_node->left = _data._nil; new_node->value = p_value; - // new_node->data=_data; + //new_node->data=_data; if (new_parent == _data._root || less(p_value, new_parent->value)) { new_parent->left = new_node; @@ -701,6 +703,12 @@ class RBSet { _copy_from(p_set); } + RBSet(std::initializer_list p_init) { + for (const T &E : p_init) { + insert(E); + } + } + _FORCE_INLINE_ RBSet() {} ~RBSet() { diff --git a/include/godot_cpp/templates/safe_refcount.hpp b/include/godot_cpp/templates/safe_refcount.hpp index e6c29689c..42cdb2444 100644 --- a/include/godot_cpp/templates/safe_refcount.hpp +++ b/include/godot_cpp/templates/safe_refcount.hpp @@ -51,7 +51,7 @@ namespace godot { #define SAFE_NUMERIC_TYPE_PUN_GUARANTEES(m_type) \ static_assert(sizeof(SafeNumeric) == sizeof(m_type)); \ static_assert(alignof(SafeNumeric) == alignof(m_type)); \ - static_assert(std::is_trivially_destructible>::value); + static_assert(std::is_trivially_destructible_v>); #define SAFE_FLAG_TYPE_PUN_GUARANTEES \ static_assert(sizeof(SafeFlag) == sizeof(bool)); \ static_assert(alignof(SafeFlag) == alignof(bool)); @@ -102,6 +102,17 @@ class SafeNumeric { return value.fetch_sub(p_value, std::memory_order_acq_rel) - p_value; } + _ALWAYS_INLINE_ T bit_or(T p_value) { + return value.fetch_or(p_value, std::memory_order_acq_rel); + } + _ALWAYS_INLINE_ T bit_and(T p_value) { + return value.fetch_and(p_value, std::memory_order_acq_rel); + } + + _ALWAYS_INLINE_ T bit_xor(T p_value) { + return value.fetch_xor(p_value, std::memory_order_acq_rel); + } + // Returns the original value instead of the new one _ALWAYS_INLINE_ T postsub(T p_value) { return value.fetch_sub(p_value, std::memory_order_acq_rel); @@ -113,7 +124,8 @@ class SafeNumeric { if (tmp >= p_value) { return tmp; // already greater, or equal } - if (value.compare_exchange_weak(tmp, p_value, std::memory_order_release)) { + + if (value.compare_exchange_weak(tmp, p_value, std::memory_order_acq_rel)) { return p_value; } } @@ -125,7 +137,7 @@ class SafeNumeric { if (c == 0) { return 0; } - if (value.compare_exchange_weak(c, c + 1, std::memory_order_release)) { + if (value.compare_exchange_weak(c, c + 1, std::memory_order_acq_rel)) { return c + 1; } } @@ -166,6 +178,16 @@ class SafeFlag { class SafeRefCount { SafeNumeric count; +#ifdef DEV_ENABLED + _ALWAYS_INLINE_ void _check_unref_safety() { + // This won't catch every misuse, but it's better than nothing. + CRASH_COND_MSG(count.get() == 0, + "Trying to unreference a SafeRefCount which is already zero is wrong and a symptom of it being misused.\n" + "Upon a SafeRefCount reaching zero any object whose lifetime is tied to it, as well as the ref count itself, must be destroyed.\n" + "Moreover, to guarantee that, no multiple threads should be racing to do the final unreferencing to zero."); + } +#endif + public: _ALWAYS_INLINE_ bool ref() { // true on success return count.conditional_increment() != 0; @@ -176,10 +198,16 @@ class SafeRefCount { } _ALWAYS_INLINE_ bool unref() { // true if must be disposed of +#ifdef DEV_ENABLED + _check_unref_safety(); +#endif return count.decrement() == 0; } _ALWAYS_INLINE_ uint32_t unrefval() { // 0 if must be disposed of +#ifdef DEV_ENABLED + _check_unref_safety(); +#endif return count.decrement(); } @@ -192,141 +220,6 @@ class SafeRefCount { } }; -#else - -template -class SafeNumeric { -protected: - T value; - -public: - _ALWAYS_INLINE_ void set(T p_value) { - value = p_value; - } - - _ALWAYS_INLINE_ T get() const { - return value; - } - - _ALWAYS_INLINE_ T increment() { - return ++value; - } - - _ALWAYS_INLINE_ T postincrement() { - return value++; - } - - _ALWAYS_INLINE_ T decrement() { - return --value; - } - - _ALWAYS_INLINE_ T postdecrement() { - return value--; - } - - _ALWAYS_INLINE_ T add(T p_value) { - return value += p_value; - } - - _ALWAYS_INLINE_ T postadd(T p_value) { - T old = value; - value += p_value; - return old; - } - - _ALWAYS_INLINE_ T sub(T p_value) { - return value -= p_value; - } - - _ALWAYS_INLINE_ T postsub(T p_value) { - T old = value; - value -= p_value; - return old; - } - - _ALWAYS_INLINE_ T exchange_if_greater(T p_value) { - if (value < p_value) { - value = p_value; - } - return value; - } - - _ALWAYS_INLINE_ T conditional_increment() { - if (value == 0) { - return 0; - } else { - return ++value; - } - } - - _ALWAYS_INLINE_ explicit SafeNumeric(T p_value = static_cast(0)) : - value(p_value) { - } -}; - -class SafeFlag { -protected: - bool flag; - -public: - _ALWAYS_INLINE_ bool is_set() const { - return flag; - } - - _ALWAYS_INLINE_ void set() { - flag = true; - } - - _ALWAYS_INLINE_ void clear() { - flag = false; - } - - _ALWAYS_INLINE_ void set_to(bool p_value) { - flag = p_value; - } - - _ALWAYS_INLINE_ explicit SafeFlag(bool p_value = false) : - flag(p_value) {} -}; - -class SafeRefCount { - uint32_t count = 0; - -public: - _ALWAYS_INLINE_ bool ref() { // true on success - if (count != 0) { - ++count; - return true; - } else { - return false; - } - } - - _ALWAYS_INLINE_ uint32_t refval() { // none-zero on success - if (count != 0) { - return ++count; - } else { - return 0; - } - } - - _ALWAYS_INLINE_ bool unref() { // true if must be disposed of - return --count == 0; - } - - _ALWAYS_INLINE_ uint32_t unrefval() { // 0 if must be disposed of - return --count; - } - - _ALWAYS_INLINE_ uint32_t get() const { - return count; - } - - _ALWAYS_INLINE_ void init(uint32_t p_value = 1) { - count = p_value; - } -}; - -#endif - } // namespace godot + +#endif // !defined(NO_THREADS) diff --git a/include/godot_cpp/templates/search_array.hpp b/include/godot_cpp/templates/search_array.hpp index aa3374e8f..27a5c9d07 100644 --- a/include/godot_cpp/templates/search_array.hpp +++ b/include/godot_cpp/templates/search_array.hpp @@ -39,12 +39,12 @@ class SearchArray { public: Comparator compare; - inline int bisect(const T *p_array, int p_len, const T &p_value, bool p_before) const { - int lo = 0; - int hi = p_len; + inline int64_t bisect(const T *p_array, int64_t p_len, const T &p_value, bool p_before) const { + int64_t lo = 0; + int64_t hi = p_len; if (p_before) { while (lo < hi) { - const int mid = (lo + hi) / 2; + const int64_t mid = (lo + hi) / 2; if (compare(p_array[mid], p_value)) { lo = mid + 1; } else { @@ -53,7 +53,7 @@ class SearchArray { } } else { while (lo < hi) { - const int mid = (lo + hi) / 2; + const int64_t mid = (lo + hi) / 2; if (compare(p_value, p_array[mid])) { hi = mid; } else { diff --git a/include/godot_cpp/templates/self_list.hpp b/include/godot_cpp/templates/self_list.hpp index cdd1f2586..4b4e3942d 100644 --- a/include/godot_cpp/templates/self_list.hpp +++ b/include/godot_cpp/templates/self_list.hpp @@ -100,11 +100,74 @@ class SelfList { p_elem->_root = nullptr; } + void clear() { + while (_first) { + remove(_first); + } + } + + void sort() { + sort_custom>(); + } + + template + void sort_custom() { + if (_first == _last) { + return; + } + + SelfList *from = _first; + SelfList *current = from; + SelfList *to = from; + + while (current) { + SelfList *next = current->_next; + + if (from != current) { + current->_prev = nullptr; + current->_next = from; + + SelfList *find = from; + C less; + while (find && less(*find->_self, *current->_self)) { + current->_prev = find; + current->_next = find->_next; + find = find->_next; + } + + if (current->_prev) { + current->_prev->_next = current; + } else { + from = current; + } + + if (current->_next) { + current->_next->_prev = current; + } else { + to = current; + } + } else { + current->_prev = nullptr; + current->_next = nullptr; + } + + current = next; + } + _first = from; + _last = to; + } + _FORCE_INLINE_ SelfList *first() { return _first; } _FORCE_INLINE_ const SelfList *first() const { return _first; } + // Forbid copying, which has broken behavior. + void operator=(const List &) = delete; + _FORCE_INLINE_ List() {} - _FORCE_INLINE_ ~List() { ERR_FAIL_COND(_first != nullptr); } + _FORCE_INLINE_ ~List() { + // A self list must be empty on destruction. + DEV_ASSERT(_first == nullptr); + } }; private: @@ -126,6 +189,9 @@ class SelfList { _FORCE_INLINE_ const SelfList *prev() const { return _prev; } _FORCE_INLINE_ T *self() const { return _self; } + // Forbid copying, which has broken behavior. + void operator=(const SelfList &) = delete; + _FORCE_INLINE_ SelfList(T *p_self) { _self = p_self; } diff --git a/include/godot_cpp/templates/sort_array.hpp b/include/godot_cpp/templates/sort_array.hpp index c2727dd94..fe1365858 100644 --- a/include/godot_cpp/templates/sort_array.hpp +++ b/include/godot_cpp/templates/sort_array.hpp @@ -78,8 +78,8 @@ class SortArray { } } - inline int bitlog(int n) const { - int k; + inline int64_t bitlog(int64_t n) const { + int64_t k; for (k = 0; n != 1; n >>= 1) { ++k; } @@ -88,8 +88,8 @@ class SortArray { /* Heap / Heapsort functions */ - inline void push_heap(int p_first, int p_hole_idx, int p_top_index, T p_value, T *p_array) const { - int parent = (p_hole_idx - 1) / 2; + inline void push_heap(int64_t p_first, int64_t p_hole_idx, int64_t p_top_index, T p_value, T *p_array) const { + int64_t parent = (p_hole_idx - 1) / 2; while (p_hole_idx > p_top_index && compare(p_array[p_first + parent], p_value)) { p_array[p_first + p_hole_idx] = p_array[p_first + parent]; p_hole_idx = parent; @@ -98,17 +98,17 @@ class SortArray { p_array[p_first + p_hole_idx] = p_value; } - inline void pop_heap(int p_first, int p_last, int p_result, T p_value, T *p_array) const { + inline void pop_heap(int64_t p_first, int64_t p_last, int64_t p_result, T p_value, T *p_array) const { p_array[p_result] = p_array[p_first]; adjust_heap(p_first, 0, p_last - p_first, p_value, p_array); } - inline void pop_heap(int p_first, int p_last, T *p_array) const { + inline void pop_heap(int64_t p_first, int64_t p_last, T *p_array) const { pop_heap(p_first, p_last - 1, p_last - 1, p_array[p_last - 1], p_array); } - inline void adjust_heap(int p_first, int p_hole_idx, int p_len, T p_value, T *p_array) const { - int top_index = p_hole_idx; - int second_child = 2 * p_hole_idx + 2; + inline void adjust_heap(int64_t p_first, int64_t p_hole_idx, int64_t p_len, T p_value, T *p_array) const { + int64_t top_index = p_hole_idx; + int64_t second_child = 2 * p_hole_idx + 2; while (second_child < p_len) { if (compare(p_array[p_first + second_child], p_array[p_first + (second_child - 1)])) { @@ -127,18 +127,18 @@ class SortArray { push_heap(p_first, p_hole_idx, top_index, p_value, p_array); } - inline void sort_heap(int p_first, int p_last, T *p_array) const { + inline void sort_heap(int64_t p_first, int64_t p_last, T *p_array) const { while (p_last - p_first > 1) { pop_heap(p_first, p_last--, p_array); } } - inline void make_heap(int p_first, int p_last, T *p_array) const { + inline void make_heap(int64_t p_first, int64_t p_last, T *p_array) const { if (p_last - p_first < 2) { return; } - int len = p_last - p_first; - int parent = (len - 2) / 2; + int64_t len = p_last - p_first; + int64_t parent = (len - 2) / 2; while (true) { adjust_heap(p_first, parent, len, p_array[p_first + parent], p_array); @@ -149,9 +149,9 @@ class SortArray { } } - inline void partial_sort(int p_first, int p_last, int p_middle, T *p_array) const { + inline void partial_sort(int64_t p_first, int64_t p_last, int64_t p_middle, T *p_array) const { make_heap(p_first, p_middle, p_array); - for (int i = p_middle; i < p_last; i++) { + for (int64_t i = p_middle; i < p_last; i++) { if (compare(p_array[i], p_array[p_first])) { pop_heap(p_first, p_middle, i, p_array[i], p_array); } @@ -159,29 +159,29 @@ class SortArray { sort_heap(p_first, p_middle, p_array); } - inline void partial_select(int p_first, int p_last, int p_middle, T *p_array) const { + inline void partial_select(int64_t p_first, int64_t p_last, int64_t p_middle, T *p_array) const { make_heap(p_first, p_middle, p_array); - for (int i = p_middle; i < p_last; i++) { + for (int64_t i = p_middle; i < p_last; i++) { if (compare(p_array[i], p_array[p_first])) { pop_heap(p_first, p_middle, i, p_array[i], p_array); } } } - inline int partitioner(int p_first, int p_last, T p_pivot, T *p_array) const { - const int unmodified_first = p_first; - const int unmodified_last = p_last; + inline int64_t partitioner(int64_t p_first, int64_t p_last, T p_pivot, T *p_array) const { + const int64_t unmodified_first = p_first; + const int64_t unmodified_last = p_last; while (true) { while (compare(p_array[p_first], p_pivot)) { - if (Validate) { + if constexpr (Validate) { ERR_BAD_COMPARE(p_first == unmodified_last - 1); } p_first++; } p_last--; while (compare(p_pivot, p_array[p_last])) { - if (Validate) { + if constexpr (Validate) { ERR_BAD_COMPARE(p_last == unmodified_first); } p_last--; @@ -196,7 +196,7 @@ class SortArray { } } - inline void introsort(int p_first, int p_last, T *p_array, int p_max_depth) const { + inline void introsort(int64_t p_first, int64_t p_last, T *p_array, int64_t p_max_depth) const { while (p_last - p_first > INTROSORT_THRESHOLD) { if (p_max_depth == 0) { partial_sort(p_first, p_last, p_last, p_array); @@ -205,7 +205,7 @@ class SortArray { p_max_depth--; - int cut = partitioner( + int64_t cut = partitioner( p_first, p_last, median_of_3( @@ -219,7 +219,7 @@ class SortArray { } } - inline void introselect(int p_first, int p_nth, int p_last, T *p_array, int p_max_depth) const { + inline void introselect(int64_t p_first, int64_t p_nth, int64_t p_last, T *p_array, int64_t p_max_depth) const { while (p_last - p_first > 3) { if (p_max_depth == 0) { partial_select(p_first, p_nth + 1, p_last, p_array); @@ -229,7 +229,7 @@ class SortArray { p_max_depth--; - int cut = partitioner( + int64_t cut = partitioner( p_first, p_last, median_of_3( @@ -248,10 +248,10 @@ class SortArray { insertion_sort(p_first, p_last, p_array); } - inline void unguarded_linear_insert(int p_last, T p_value, T *p_array) const { - int next = p_last - 1; + inline void unguarded_linear_insert(int64_t p_last, T p_value, T *p_array) const { + int64_t next = p_last - 1; while (compare(p_value, p_array[next])) { - if (Validate) { + if constexpr (Validate) { ERR_BAD_COMPARE(next == 0); } p_array[p_last] = p_array[next]; @@ -261,10 +261,10 @@ class SortArray { p_array[p_last] = p_value; } - inline void linear_insert(int p_first, int p_last, T *p_array) const { + inline void linear_insert(int64_t p_first, int64_t p_last, T *p_array) const { T val = p_array[p_last]; if (compare(val, p_array[p_first])) { - for (int i = p_last; i > p_first; i--) { + for (int64_t i = p_last; i > p_first; i--) { p_array[i] = p_array[i - 1]; } @@ -274,22 +274,22 @@ class SortArray { } } - inline void insertion_sort(int p_first, int p_last, T *p_array) const { + inline void insertion_sort(int64_t p_first, int64_t p_last, T *p_array) const { if (p_first == p_last) { return; } - for (int i = p_first + 1; i != p_last; i++) { + for (int64_t i = p_first + 1; i != p_last; i++) { linear_insert(p_first, i, p_array); } } - inline void unguarded_insertion_sort(int p_first, int p_last, T *p_array) const { - for (int i = p_first; i != p_last; i++) { + inline void unguarded_insertion_sort(int64_t p_first, int64_t p_last, T *p_array) const { + for (int64_t i = p_first; i != p_last; i++) { unguarded_linear_insert(i, p_array[i], p_array); } } - inline void final_insertion_sort(int p_first, int p_last, T *p_array) const { + inline void final_insertion_sort(int64_t p_first, int64_t p_last, T *p_array) const { if (p_last - p_first > INTROSORT_THRESHOLD) { insertion_sort(p_first, p_first + INTROSORT_THRESHOLD, p_array); unguarded_insertion_sort(p_first + INTROSORT_THRESHOLD, p_last, p_array); @@ -298,18 +298,18 @@ class SortArray { } } - inline void sort_range(int p_first, int p_last, T *p_array) const { + inline void sort_range(int64_t p_first, int64_t p_last, T *p_array) const { if (p_first != p_last) { introsort(p_first, p_last, p_array, bitlog(p_last - p_first) * 2); final_insertion_sort(p_first, p_last, p_array); } } - inline void sort(T *p_array, int p_len) const { + inline void sort(T *p_array, int64_t p_len) const { sort_range(0, p_len, p_array); } - inline void nth_element(int p_first, int p_last, int p_nth, T *p_array) const { + inline void nth_element(int64_t p_first, int64_t p_last, int64_t p_nth, T *p_array) const { if (p_first == p_last || p_nth == p_last) { return; } diff --git a/include/godot_cpp/templates/vector.hpp b/include/godot_cpp/templates/vector.hpp index 50593f30a..51eb825d3 100644 --- a/include/godot_cpp/templates/vector.hpp +++ b/include/godot_cpp/templates/vector.hpp @@ -68,6 +68,7 @@ class Vector { CowData _cowdata; public: + // Must take a copy instead of a reference (see GH-31736). bool push_back(T p_elem); _FORCE_INLINE_ bool append(const T &p_elem) { return push_back(p_elem); } //alias void fill(T p_elem); @@ -96,11 +97,13 @@ class Vector { Error resize(Size p_size) { return _cowdata.resize(p_size); } Error resize_zeroed(Size p_size) { return _cowdata.template resize(p_size); } _FORCE_INLINE_ const T &operator[](Size p_index) const { return _cowdata.get(p_index); } + // Must take a copy instead of a reference (see GH-31736). Error insert(Size p_pos, T p_val) { return _cowdata.insert(p_pos, p_val); } Size find(const T &p_val, Size p_from = 0) const { return _cowdata.find(p_val, p_from); } Size rfind(const T &p_val, Size p_from = -1) const { return _cowdata.rfind(p_val, p_from); } Size count(const T &p_val) const { return _cowdata.count(p_val); } + // Must take a copy instead of a reference (see GH-31736). void append_array(Vector p_other); _FORCE_INLINE_ bool has(const T &p_val) const { return find(p_val) != -1; } @@ -145,17 +148,19 @@ class Vector { insert(i, p_val); } - inline void operator=(const Vector &p_from) { - _cowdata._ref(p_from._cowdata); - } + void operator=(const Vector &p_from) { _cowdata._ref(p_from._cowdata); } + void operator=(Vector &&p_from) { _cowdata = std::move(p_from._cowdata); } Vector to_byte_array() const { Vector ret; if (is_empty()) { return ret; } - ret.resize(size() * sizeof(T)); - memcpy(ret.ptrw(), ptr(), sizeof(T) * size()); + size_t alloc_size = size() * sizeof(T); + ret.resize(alloc_size); + if (alloc_size) { + memcpy(ret.ptrw(), ptr(), alloc_size); + } return ret; } @@ -278,16 +283,11 @@ class Vector { } _FORCE_INLINE_ Vector() {} - _FORCE_INLINE_ Vector(std::initializer_list p_init) { - Error err = _cowdata.resize(p_init.size()); - ERR_FAIL_COND(err); - - Size i = 0; - for (const T &element : p_init) { - _cowdata.set(i++, element); - } - } + _FORCE_INLINE_ Vector(std::initializer_list p_init) : + _cowdata(p_init) {} _FORCE_INLINE_ Vector(const Vector &p_from) { _cowdata._ref(p_from._cowdata); } + _FORCE_INLINE_ Vector(Vector &&p_from) : + _cowdata(std::move(p_from._cowdata)) {} _FORCE_INLINE_ ~Vector() {} }; diff --git a/include/godot_cpp/templates/vmap.hpp b/include/godot_cpp/templates/vmap.hpp index b6fa3c039..ea043d9fc 100644 --- a/include/godot_cpp/templates/vmap.hpp +++ b/include/godot_cpp/templates/vmap.hpp @@ -72,16 +72,16 @@ class VMap { middle = (low + high) / 2; if (p_val < a[middle].key) { - high = middle - 1; // search low end of array + high = middle - 1; //search low end of array } else if (a[middle].key < p_val) { - low = middle + 1; // search high end of array + low = middle + 1; //search high end of array } else { r_exact = true; return middle; } } - // return the position where this would be inserted + //return the position where this would be inserted if (a[middle].key < p_val) { middle++; } @@ -102,9 +102,9 @@ class VMap { middle = (low + high) / 2; if (p_val < a[middle].key) { - high = middle - 1; // search low end of array + high = middle - 1; //search low end of array } else if (a[middle].key < p_val) { - low = middle + 1; // search high end of array + low = middle + 1; //search high end of array } else { return middle; } @@ -142,6 +142,9 @@ class VMap { } int find_nearest(const T &p_val) const { + if (_cowdata.is_empty()) { + return -1; + } bool exact; return _find(p_val, exact); } @@ -191,6 +194,8 @@ class VMap { } _FORCE_INLINE_ VMap() {} + _FORCE_INLINE_ VMap(std::initializer_list p_init) : + _cowdata(p_init) {} _FORCE_INLINE_ VMap(const VMap &p_from) { _cowdata._ref(p_from._cowdata); } inline void operator=(const VMap &p_from) { diff --git a/include/godot_cpp/templates/vset.hpp b/include/godot_cpp/templates/vset.hpp index 42167403b..13d6e996b 100644 --- a/include/godot_cpp/templates/vset.hpp +++ b/include/godot_cpp/templates/vset.hpp @@ -59,16 +59,16 @@ class VSet { middle = (low + high) / 2; if (p_val < a[middle]) { - high = middle - 1; // search low end of array + high = middle - 1; //search low end of array } else if (a[middle] < p_val) { - low = middle + 1; // search high end of array + low = middle + 1; //search high end of array } else { r_exact = true; return middle; } } - // return the position where this would be inserted + //return the position where this would be inserted if (a[middle] < p_val) { middle++; } @@ -89,9 +89,9 @@ class VSet { middle = (low + high) / 2; if (p_val < a[middle]) { - high = middle - 1; // search low end of array + high = middle - 1; //search low end of array } else if (a[middle] < p_val) { - low = middle + 1; // search high end of array + low = middle + 1; //search high end of array } else { return middle; } @@ -137,6 +137,10 @@ class VSet { inline const T &operator[](int p_index) const { return _data[p_index]; } + + _FORCE_INLINE_ VSet() {} + _FORCE_INLINE_ VSet(std::initializer_list p_init) : + _data(p_init) {} }; } // namespace godot