diff --git a/include/godot_cpp/core/error_macros.hpp b/include/godot_cpp/core/error_macros.hpp index 16e9885f6..22b71d72c 100644 --- a/include/godot_cpp/core/error_macros.hpp +++ b/include/godot_cpp/core/error_macros.hpp @@ -48,6 +48,10 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool p_editor_notify = false, bool p_fatal = false); void _err_flush_stdout(); +// Normally present in print_string.hpp, added here because our include situation is +// different from upstream godot and some files might otherwise not compile. +bool is_print_verbose_enabled(); + } // namespace godot #ifdef __GNUC__ @@ -708,6 +712,16 @@ void _err_flush_stdout(); } else \ ((void)0) +/** + * Warns about `m_msg` only when verbose mode is enabled. + */ +#define WARN_VERBOSE(m_msg) \ + { \ + if (is_print_verbose_enabled()) { \ + WARN_PRINT(m_msg); \ + } \ + } + // Print deprecated warning message macros. /** diff --git a/include/godot_cpp/core/memory.hpp b/include/godot_cpp/core/memory.hpp index ea3b709a4..cf3f193ea 100644 --- a/include/godot_cpp/core/memory.hpp +++ b/include/godot_cpp/core/memory.hpp @@ -199,6 +199,42 @@ T *memnew_arr_template(size_t p_elements) { return (T *)mem; } +// Fast alternative to a loop constructor pattern. +template +_FORCE_INLINE_ void memnew_arr_placement(T *p_start, size_t p_num) { + if constexpr (is_zero_constructible_v) { + // Can optimize with memset. + memset(static_cast(p_start), 0, p_num * sizeof(T)); + } else { + // Need to use a for loop. + for (size_t i = 0; i < p_num; i++) { + memnew_placement(p_start + i, T()); + } + } +} + +// Convenient alternative to a loop copy pattern. +template +_FORCE_INLINE_ void copy_arr_placement(T *p_dst, const T *p_src, size_t p_num) { + if constexpr (std::is_trivially_copyable_v) { + memcpy((uint8_t *)p_dst, (uint8_t *)p_src, p_num * sizeof(T)); + } else { + for (size_t i = 0; i < p_num; i++) { + memnew_placement(p_dst + i, T(p_src[i])); + } + } +} + +// Convenient alternative to a loop destructor pattern. +template +_FORCE_INLINE_ void destruct_arr_placement(T *p_dst, size_t p_num) { + if constexpr (!std::is_trivially_destructible_v) { + for (size_t i = 0; i < p_num; i++) { + p_dst[i].~T(); + } + } +} + template size_t memarr_len(const T *p_class) { uint8_t *ptr = (uint8_t *)p_class; diff --git a/include/godot_cpp/templates/cowdata.hpp b/include/godot_cpp/templates/cowdata.hpp index d8c7456b1..6c88b689e 100644 --- a/include/godot_cpp/templates/cowdata.hpp +++ b/include/godot_cpp/templates/cowdata.hpp @@ -43,130 +43,87 @@ #include #include -namespace godot { - -template -class Vector; - -template -class VMap; +#ifdef ASAN_ENABLED +#include +#endif -template -class CharStringT; +namespace godot { static_assert(std::is_trivially_destructible_v>); -// Silence a false positive warning (see GH-52119). -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wplacement-new" +// Silences false-positive warnings. +GODOT_GCC_WARNING_PUSH +GODOT_GCC_WARNING_IGNORE("-Wplacement-new") // Silence a false positive warning (see GH-52119). +GODOT_GCC_WARNING_IGNORE("-Wmaybe-uninitialized") // False positive raised when using constexpr. +GODOT_GCC_WARNING_IGNORE("-Warray-bounds") +GODOT_GCC_WARNING_IGNORE("-Wrestrict") +GODOT_GCC_PRAGMA(GCC diagnostic warning "-Wstringop-overflow=0") // Can't "ignore" this for some reason. +#ifdef WINDOWS_ENABLED +GODOT_GCC_PRAGMA(GCC diagnostic warning "-Wdangling-pointer=0") // Can't "ignore" this for some reason. #endif template class CowData { - template - friend class Vector; - - template - friend class VMap; - - template - friend class CharStringT; - public: typedef int64_t Size; typedef uint64_t USize; static constexpr USize MAX_INT = INT64_MAX; private: - // Function to find the next power of 2 to an integer. - static _FORCE_INLINE_ USize next_po2(USize x) { - if (x == 0) { - return 0; - } - - --x; - x |= x >> 1; - x |= x >> 2; - x |= x >> 4; - x |= x >> 8; - x |= x >> 16; - if (sizeof(USize) == 8) { - x |= x >> 32; - } - - return ++x; - } - - // Alignment: ↓ max_align_t ↓ USize ↓ MAX_ALIGN - // ┌────────────────────┬──┬─────────────┬──┬───────────... - // │ SafeNumeric │░░│ USize │░░│ T[] - // │ ref. count │░░│ data size │░░│ data - // └────────────────────┴──┴─────────────┴──┴───────────... - // Offset: ↑ REF_COUNT_OFFSET ↑ SIZE_OFFSET ↑ DATA_OFFSET + // Alignment: ↓ max_align_t ↓ USize ↓ USize ↓ MAX_ALIGN + // ┌────────────────────┬──┬───────────────┬──┬─────────────┬──┬───────────... + // │ SafeNumeric │░░│ USize │░░│ USize │░░│ T[] + // │ ref. count │░░│ data capacity │░░│ data size │░░│ data + // └────────────────────┴──┴───────────────┴──┴─────────────┴──┴───────────... + // Offset: ↑ REF_COUNT_OFFSET ↑ CAPACITY_OFFSET ↑ SIZE_OFFSET ↑ DATA_OFFSET static constexpr size_t REF_COUNT_OFFSET = 0; - static constexpr size_t SIZE_OFFSET = ((REF_COUNT_OFFSET + sizeof(SafeNumeric)) % alignof(USize) == 0) ? (REF_COUNT_OFFSET + sizeof(SafeNumeric)) : ((REF_COUNT_OFFSET + sizeof(SafeNumeric)) + alignof(USize) - ((REF_COUNT_OFFSET + sizeof(SafeNumeric)) % alignof(USize))); - static constexpr size_t DATA_OFFSET = ((SIZE_OFFSET + sizeof(USize)) % Memory::MAX_ALIGN == 0) ? (SIZE_OFFSET + sizeof(USize)) : ((SIZE_OFFSET + sizeof(USize)) + Memory::MAX_ALIGN - ((SIZE_OFFSET + sizeof(USize)) % Memory::MAX_ALIGN)); + static constexpr size_t CAPACITY_OFFSET = Memory::get_aligned_address(REF_COUNT_OFFSET + sizeof(SafeNumeric), alignof(USize)); + static constexpr size_t SIZE_OFFSET = Memory::get_aligned_address(CAPACITY_OFFSET + sizeof(USize), alignof(USize)); + static constexpr size_t DATA_OFFSET = Memory::get_aligned_address(SIZE_OFFSET + sizeof(USize), Memory::MAX_ALIGN); mutable T *_ptr = nullptr; // internal helpers - static _FORCE_INLINE_ SafeNumeric *_get_refcount_ptr(uint8_t *p_ptr) { - return (SafeNumeric *)(p_ptr + REF_COUNT_OFFSET); + static constexpr _FORCE_INLINE_ USize grow_capacity(USize p_previous_capacity) { + // 1.5x the given size. + // This ratio was chosen because it is close to the ideal growth rate of the golden ratio. + // See https://archive.ph/Z2R8w for details. + return MAX((USize)2, p_previous_capacity + ((1 + p_previous_capacity) >> 1)); + } + + static constexpr _FORCE_INLINE_ USize next_capacity(USize p_previous_capacity, USize p_size) { + if (p_previous_capacity < p_size) { + return MAX(grow_capacity(p_previous_capacity), p_size); + } + return p_previous_capacity; } - static _FORCE_INLINE_ USize *_get_size_ptr(uint8_t *p_ptr) { - return (USize *)(p_ptr + SIZE_OFFSET); + static constexpr _FORCE_INLINE_ USize smaller_capacity(USize p_previous_capacity, USize p_size) { + if (p_size < p_previous_capacity >> 2) { + return grow_capacity(p_size); + } + return p_previous_capacity; } static _FORCE_INLINE_ T *_get_data_ptr(uint8_t *p_ptr) { return (T *)(p_ptr + DATA_OFFSET); } + /// Note: Assumes _ptr != nullptr. _FORCE_INLINE_ SafeNumeric *_get_refcount() const { - if (!_ptr) { - return nullptr; - } - return (SafeNumeric *)((uint8_t *)_ptr - DATA_OFFSET + REF_COUNT_OFFSET); } + /// Note: Assumes _ptr != nullptr. _FORCE_INLINE_ USize *_get_size() const { - if (!_ptr) { - return nullptr; - } - return (USize *)((uint8_t *)_ptr - DATA_OFFSET + SIZE_OFFSET); } - _FORCE_INLINE_ USize _get_alloc_size(USize p_elements) const { - return next_po2(p_elements * sizeof(T)); - } - - _FORCE_INLINE_ bool _get_alloc_size_checked(USize p_elements, USize *out) const { - if (unlikely(p_elements == 0)) { - *out = 0; - return true; - } -#if defined(__GNUC__) && defined(IS_32_BIT) - USize o; - USize p; - if (__builtin_mul_overflow(p_elements, sizeof(T), &o)) { - *out = 0; - return false; - } - *out = next_po2(o); - if (__builtin_add_overflow(o, static_cast(32), &p)) { - return false; // No longer allocated here. - } -#else - // Speed is more important than correctness here, do the operations unchecked - // and hope for the best. - *out = _get_alloc_size(p_elements); -#endif - return *out; + /// Note: Assumes _ptr != nullptr. + _FORCE_INLINE_ USize *_get_capacity() const { + return (USize *)((uint8_t *)_ptr - DATA_OFFSET + CAPACITY_OFFSET); } // Decrements the reference count. Deallocates the backing buffer if needed. @@ -174,8 +131,29 @@ class CowData { void _unref(); void _ref(const CowData *p_from); void _ref(const CowData &p_from); - USize _copy_on_write(); - Error _realloc(Size p_alloc_size); + + /// Allocates a backing array of the given capacity. The reference count is initialized to 1, size to 0. + /// It is the responsibility of the caller to: + /// - Ensure _ptr == nullptr + /// - Ensure p_capacity > 0 + Error _alloc_exact(USize p_capacity); + + /// Re-allocates the backing array to the given capacity. + /// It is the responsibility of the caller to: + /// - Ensure we are the only owner of the backing array + /// - Ensure p_capacity > 0 + Error _realloc_exact(USize p_capacity); + + /// Create a new buffer and copies over elements from the old buffer. + /// Elements are inserted first from the start, then a gap is left uninitialized, and then elements are inserted from the back. + /// It is the responsibility of the caller to: + /// - Construct elements in the gap. + /// - Ensure size() >= p_size_from_start and size() >= p_size_from_back. + /// - Ensure p_capacity is enough to hold all elements. + [[nodiscard]] Error _copy_to_new_buffer_exact(USize p_capacity, USize p_size_from_start, USize p_gap, USize p_size_from_back); + + /// Ensure we are the only owners of the backing buffer. + [[nodiscard]] Error _copy_on_write(); public: void operator=(const CowData &p_from) { _ref(p_from); } @@ -189,83 +167,65 @@ class CowData { p_from._ptr = nullptr; } - _FORCE_INLINE_ T *ptrw() { - _copy_on_write(); + _FORCE_INLINE_ T *ptrw() _LIFETIME_BOUND_ { + // If forking fails, we can only crash. + CRASH_COND(_copy_on_write()); return _ptr; } - _FORCE_INLINE_ const T *ptr() const { + _FORCE_INLINE_ const T *ptr() const _LIFETIME_BOUND_ { return _ptr; } - _FORCE_INLINE_ Size size() const { - USize *size = (USize *)_get_size(); - if (size) { - return *size; - } else { - return 0; - } - } + _FORCE_INLINE_ Size size() const { return !_ptr ? 0 : *_get_size(); } + _FORCE_INLINE_ USize capacity() const { return !_ptr ? 0 : *_get_capacity(); } + _FORCE_INLINE_ USize refcount() const { return !_ptr ? 0 : *_get_refcount(); } - _FORCE_INLINE_ void clear() { resize(0); } - _FORCE_INLINE_ bool is_empty() const { return _ptr == nullptr; } + _FORCE_INLINE_ void clear() { _unref(); } + _FORCE_INLINE_ bool is_empty() const { return size() == 0; } _FORCE_INLINE_ void set(Size p_index, const T &p_elem) { ERR_FAIL_INDEX(p_index, size()); - _copy_on_write(); + // TODO Returning the error would be more appropriate. + CRASH_COND(_copy_on_write()); _ptr[p_index] = p_elem; } - _FORCE_INLINE_ T &get_m(Size p_index) { + _FORCE_INLINE_ T &get_m(Size p_index) _LIFETIME_BOUND_ { CRASH_BAD_INDEX(p_index, size()); - _copy_on_write(); + // If we fail to fork, all we can do is crash, + // since the caller may write incorrectly to the unforked array. + CRASH_COND(_copy_on_write()); return _ptr[p_index]; } - _FORCE_INLINE_ const T &get(Size p_index) const { + _FORCE_INLINE_ const T &get(Size p_index) const _LIFETIME_BOUND_ { CRASH_BAD_INDEX(p_index, size()); return _ptr[p_index]; } - template + template Error resize(Size p_size); - _FORCE_INLINE_ void remove_at(Size p_index) { - ERR_FAIL_INDEX(p_index, size()); - T *p = ptrw(); - Size len = size(); - for (Size i = p_index; i < len - 1; i++) { - p[i] = std::move(p[i + 1]); - } - - resize(len - 1); + template + Error reserve(USize p_min_capacity); + _FORCE_INLINE_ Error reserve_exact(USize p_capacity) { + return reserve(p_capacity); } - Error insert(Size p_pos, const T &p_val) { - 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]); - } - p[p_pos] = p_val; + _FORCE_INLINE_ void remove_at(Size p_index); - return OK; - } - - _FORCE_INLINE_ operator Span() const { return Span(ptr(), size()); } - _FORCE_INLINE_ Span span() const { return operator Span(); } + Error insert(Size p_pos, T &&p_val); + Error push_back(T &&p_val); - Size find(const T &p_val, Size p_from = 0) const; - Size rfind(const T &p_val, Size p_from = -1) const; - Size count(const T &p_val) const; + _FORCE_INLINE_ operator Span() const _LIFETIME_BOUND_ { return Span(ptr(), size()); } + _FORCE_INLINE_ Span span() const _LIFETIME_BOUND_ { return operator Span(); } _FORCE_INLINE_ CowData() {} _FORCE_INLINE_ ~CowData() { _unref(); } _FORCE_INLINE_ CowData(std::initializer_list p_init); + _FORCE_INLINE_ explicit CowData(Span p_span); _FORCE_INLINE_ CowData(const CowData &p_from) { _ref(p_from); } _FORCE_INLINE_ CowData(CowData &&p_from) { _ptr = p_from._ptr; @@ -279,13 +239,13 @@ void CowData::_unref() { return; } - SafeNumeric *refc = _get_refcount(); - if (refc->decrement() > 0) { + if (_get_refcount()->decrement() > 0) { // Data is still in use elsewhere. _ptr = nullptr; return; } - // Clean up. + // We had the only reference; destroy the data. + // 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. @@ -293,203 +253,305 @@ void CowData::_unref() { // 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(); + USize current_size = size(); T *prev_ptr = _ptr; _ptr = nullptr; - if constexpr (!std::is_trivially_destructible_v) { - for (USize i = 0; i < current_size; ++i) { - prev_ptr[i].~T(); - } - } +#ifdef ASAN_ENABLED + // Access during destruction is illegal in C++, and results in undefined behavior. + // In address sanitizer builds, we can poison ourselves (_ptr) to catch this. + __asan_poison_memory_region(this, sizeof(CowData)); +#endif + + destruct_arr_placement(prev_ptr, current_size); - // free mem + // Free Memory. Memory::free_static((uint8_t *)prev_ptr - DATA_OFFSET, false); + +#ifdef ASAN_ENABLED + __asan_unpoison_memory_region(this, sizeof(CowData)); +#elif defined(DEBUG_ENABLED) + // In a non-asan build, the best we can do is catch if elements were added during destruction. + ERR_FAIL_COND_MSG(_ptr != nullptr, "Internal bug, please report: CowData was modified during destruction."); +#endif } template -typename CowData::USize CowData::_copy_on_write() { - if (!_ptr) { - return 0; +void CowData::remove_at(Size p_index) { + const Size prev_size = size(); + ERR_FAIL_INDEX(p_index, prev_size); + + if (prev_size == 1) { + // Removing the only element. + _unref(); + return; } - SafeNumeric *refc = _get_refcount(); + const USize new_size = prev_size - 1; - USize rc = refc->get(); - if (unlikely(rc > 1)) { - /* in use by more than me */ - USize current_size = *_get_size(); + if (_get_refcount()->get() == 1) { + // We're the only owner; remove in-place. - uint8_t *mem_new = (uint8_t *)Memory::alloc_static(_get_alloc_size(current_size) + DATA_OFFSET, false); - ERR_FAIL_NULL_V(mem_new, 0); + // Destruct the element, then relocate the rest one down. + _ptr[p_index].~T(); + memmove((void *)(_ptr + p_index), (void *)(_ptr + p_index + 1), (new_size - p_index) * sizeof(T)); - SafeNumeric *_refc_ptr = _get_refcount_ptr(mem_new); - USize *_size_ptr = _get_size_ptr(mem_new); - T *_data_ptr = _get_data_ptr(mem_new); + // Shrink to fit if necessary. + const USize new_capacity = smaller_capacity(capacity(), new_size); + if (new_capacity < capacity()) { + Error err = _realloc_exact(new_capacity); + CRASH_COND(err); + } + *_get_size() = new_size; + } else { + // Remove by forking. + Error err = _copy_to_new_buffer_exact(smaller_capacity(capacity(), new_size), p_index, 0, new_size - p_index); + CRASH_COND(err); + } +} - new (_refc_ptr) SafeNumeric(1); //refcount - *(_size_ptr) = current_size; //size +template +Error CowData::insert(Size p_pos, T &&p_val) { + const Size new_size = size() + 1; + ERR_FAIL_INDEX_V(p_pos, new_size, ERR_INVALID_PARAMETER); - // initialize new elements - if constexpr (std::is_trivially_copyable_v) { - memcpy((uint8_t *)_data_ptr, _ptr, current_size * sizeof(T)); - } else { - for (USize i = 0; i < current_size; i++) { - memnew_placement(&_data_ptr[i], T(_ptr[i])); + if (!_ptr) { + _alloc_exact(next_capacity(0, 1)); + *_get_size() = 1; + } else if (_get_refcount()->get() == 1) { + if ((USize)new_size > capacity()) { + // Need to grow. + const Error error = _realloc_exact(grow_capacity(capacity())); + if (error) { + return error; } } - _unref(); - _ptr = _data_ptr; - - rc = 1; + // Relocate elements one position up. + memmove((void *)(_ptr + p_pos + 1), (void *)(_ptr + p_pos), (size() - p_pos) * sizeof(T)); + *_get_size() = new_size; + } else { + // Insert new element by forking. + // Use the max of capacity and new_size, to ensure we don't accidentally shrink after reserve. + const USize new_capacity = next_capacity(capacity(), new_size); + const Error error = _copy_to_new_buffer_exact(new_capacity, p_pos, 1, size() - p_pos); + if (error) { + return error; + } } - return rc; + + // Create the new element at the given index. + memnew_placement(_ptr + p_pos, T(std::move(p_val))); + + return OK; } template -template -Error CowData::resize(Size p_size) { - ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER); +Error CowData::push_back(T &&p_val) { + const Size new_size = size() + 1; - Size current_size = size(); + if (!_ptr) { + // Grow by allocating. + _alloc_exact(next_capacity(0, 1)); + *_get_size() = 1; + } else if (_get_refcount()->get() == 1) { + // Grow in-place. + if ((USize)new_size > capacity()) { + // Need to grow. + const Error error = _realloc_exact(grow_capacity(capacity())); + if (error) { + return error; + } + } - if (p_size == current_size) { - return OK; + *_get_size() = new_size; + } else { + // Grow by forking. + // Use the max of capacity and new_size, to ensure we don't accidentally shrink after reserve. + const USize new_capacity = next_capacity(capacity(), new_size); + const Error error = _copy_to_new_buffer_exact(new_capacity, size(), 1, 0); + if (error) { + return error; + } } - if (p_size == 0) { - // Wants to clean up. - _unref(); // Resets _ptr to nullptr. - return OK; - } + // Create the new element at the given index. + memnew_placement(_ptr + new_size - 1, T(std::move(p_val))); - // possibly changing size, copy on write - _copy_on_write(); + return OK; +} - USize current_alloc_size = _get_alloc_size(current_size); - USize alloc_size; - ERR_FAIL_COND_V(!_get_alloc_size_checked(p_size, &alloc_size), ERR_OUT_OF_MEMORY); +template +template +Error CowData::reserve(USize p_min_capacity) { + USize new_capacity = p_exact ? p_min_capacity : next_capacity(capacity(), p_min_capacity); + if (new_capacity <= capacity()) { + if (p_min_capacity < (USize)size()) { + WARN_VERBOSE("reserve() called with a capacity smaller than the current size. This is likely a mistake."); + } + // No need to reserve more, we already have (at least) the right size. + return OK; + } - if (p_size > current_size) { - if (alloc_size != current_alloc_size) { - if (current_size == 0) { - // alloc from scratch - uint8_t *mem_new = (uint8_t *)Memory::alloc_static(alloc_size + DATA_OFFSET, false); - ERR_FAIL_NULL_V(mem_new, ERR_OUT_OF_MEMORY); + if (!_ptr) { + // Initial allocation. + return _alloc_exact(new_capacity); + } else if (_get_refcount()->get() == 1) { + // Grow in-place. + return _realloc_exact(new_capacity); + } else { + // Grow by forking. + return _copy_to_new_buffer_exact(new_capacity, size(), 0, 0); + } +} - SafeNumeric *_refc_ptr = _get_refcount_ptr(mem_new); - USize *_size_ptr = _get_size_ptr(mem_new); - T *_data_ptr = _get_data_ptr(mem_new); +template +template +Error CowData::resize(Size p_size) { + ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER); - new (_refc_ptr) SafeNumeric(1); //refcount - *(_size_ptr) = 0; //size, currently none + const Size prev_size = size(); + if (p_size == prev_size) { + // Caller wants to stay the same size, so we do nothing. + return OK; + } - _ptr = _data_ptr; + if (p_size > prev_size) { + // Caller wants to grow. - } else { - const Error error = _realloc(alloc_size); + if (!_ptr) { + // Grow by allocating. + const Error error = _alloc_exact(next_capacity(0, p_size)); + if (error) { + return error; + } + } else if (_get_refcount()->get() == 1) { + // Grow in-place. + if ((USize)p_size > capacity()) { + const Error error = _realloc_exact(next_capacity(capacity(), p_size)); if (error) { return error; } } - } - - // construct the newly created elements - - if constexpr (!std::is_trivially_constructible_v) { - for (Size i = *_get_size(); i < p_size; i++) { - memnew_placement(&_ptr[i], T); + } else { + // Grow by forking. + const Error error = _copy_to_new_buffer_exact(next_capacity(capacity(), p_size), prev_size, 0, 0); + if (error) { + return error; } - } else if (p_ensure_zero) { - memset((void *)(_ptr + current_size), 0, (p_size - current_size) * sizeof(T)); } + // Construct new elements. + if constexpr (p_initialize) { + memnew_arr_placement(_ptr + prev_size, p_size - prev_size); + } *_get_size() = p_size; - } else if (p_size < current_size) { - if constexpr (!std::is_trivially_destructible_v) { - // deinitialize no longer needed elements - for (USize i = p_size; i < *_get_size(); i++) { - T *t = &_ptr[i]; - t->~T(); + return OK; + } else { + // Caller wants to shrink. + + if (p_size == 0) { + _unref(); + return OK; + } else if (_get_refcount()->get() == 1) { + // Shrink in-place. + destruct_arr_placement(_ptr + p_size, prev_size - p_size); + + // Shrink buffer if necessary. + const USize new_capacity = smaller_capacity(capacity(), p_size); + if (new_capacity < capacity()) { + Error err = _realloc_exact(new_capacity); + CRASH_COND(err); } - } - if (alloc_size != current_alloc_size) { - const Error error = _realloc(alloc_size); - if (error) { - return error; - } + *_get_size() = p_size; + return OK; + } else { + // Shrink by forking. + const USize new_capacity = smaller_capacity(capacity(), p_size); + return _copy_to_new_buffer_exact(new_capacity, p_size, 0, 0); } - - *_get_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); +Error CowData::_alloc_exact(USize p_capacity) { + DEV_ASSERT(!_ptr); + + uint8_t *mem_new = (uint8_t *)Memory::alloc_static(p_capacity * sizeof(T) + 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); + _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; + // If we alloc, we're guaranteed to be the only reference. + new (_get_refcount()) SafeNumeric(1); + *_get_size() = 0; + // The actual capacity is whatever we can stuff into the alloc_size. + *_get_capacity() = p_capacity; return OK; } template -typename CowData::Size CowData::find(const T &p_val, Size p_from) const { - Size ret = -1; +Error CowData::_realloc_exact(USize p_capacity) { + DEV_ASSERT(_ptr); - if (p_from < 0 || size() == 0) { - return ret; - } + uint8_t *mem_new = (uint8_t *)Memory::realloc_static(((uint8_t *)_ptr) - DATA_OFFSET, p_capacity * sizeof(T) + DATA_OFFSET, false); + ERR_FAIL_NULL_V(mem_new, ERR_OUT_OF_MEMORY); - for (Size i = p_from; i < size(); i++) { - if (get(i) == p_val) { - ret = i; - break; - } - } + _ptr = _get_data_ptr(mem_new); - return ret; + // If we realloc, we're guaranteed to be the only reference. + // So the reference was 1 and was copied to be 1 again. + DEV_ASSERT(_get_refcount()->get() == 1); + // The size was also copied from the previous allocation. + // The actual capacity is whatever we can stuff into the alloc_size. + *_get_capacity() = p_capacity; + + return OK; } template -typename CowData::Size CowData::rfind(const T &p_val, Size p_from) const { - const Size s = size(); +Error CowData::_copy_to_new_buffer_exact(USize p_capacity, USize p_size_from_start, USize p_gap, USize p_size_from_back) { + DEV_ASSERT(p_capacity >= p_size_from_start + p_size_from_back + p_gap); + DEV_ASSERT((USize)size() >= p_size_from_start && (USize)size() >= p_size_from_back); + + // Create a temporary CowData to hold ownership over our _ptr. + // It will be used to copy elements from the old buffer over to our new buffer. + // At the end of the block, it will be automatically destructed by going out of scope. + const CowData prev_data; + prev_data._ptr = _ptr; + _ptr = nullptr; - if (p_from < 0) { - p_from = s + p_from; - } - if (p_from < 0 || p_from >= s) { - p_from = s - 1; + const Error error = _alloc_exact(p_capacity); + if (error) { + // On failure to allocate, recover the old data and return the error. + _ptr = prev_data._ptr; + prev_data._ptr = nullptr; + return error; } - for (Size i = p_from; i >= 0; i--) { - if (get(i) == p_val) { - return i; - } - } - return -1; + // Copy over elements. + copy_arr_placement(_ptr, prev_data._ptr, p_size_from_start); + copy_arr_placement( + _ptr + p_size_from_start + p_gap, + prev_data._ptr + prev_data.size() - p_size_from_back, + p_size_from_back); + *_get_size() = p_size_from_start + p_gap + p_size_from_back; + + return OK; } template -typename CowData::Size CowData::count(const T &p_val) const { - Size amount = 0; - for (Size i = 0; i < size(); i++) { - if (get(i) == p_val) { - amount++; - } +Error CowData::_copy_on_write() { + if (!_ptr || _get_refcount()->get() == 1) { + // Nothing to do. + return OK; } - return amount; + + // Fork to become the only reference. + return _copy_to_new_buffer_exact(capacity(), size(), 0, 0); } template @@ -516,19 +578,24 @@ void CowData::_ref(const CowData &p_from) { template CowData::CowData(std::initializer_list p_init) { - Error err = resize(p_init.size()); - if (err != OK) { - return; - } + CRASH_COND(_alloc_exact(p_init.size())); - Size i = 0; - for (const T &element : p_init) { - set(i++, element); - } + copy_arr_placement(_ptr, p_init.begin(), p_init.size()); + *_get_size() = p_init.size(); } -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif +template +CowData::CowData(Span p_span) { + CRASH_COND(_alloc_exact(p_span.size())); + + copy_arr_placement(_ptr, p_span.begin(), p_span.size()); + *_get_size() = p_span.size(); +} + +GODOT_GCC_WARNING_POP + +// Zero-constructing CowData initializes _ptr to nullptr (and thus empty). +template +struct is_zero_constructible> : std::true_type {}; } // namespace godot diff --git a/include/godot_cpp/templates/vector.hpp b/include/godot_cpp/templates/vector.hpp index c65d9d984..7ef44c2b8 100644 --- a/include/godot_cpp/templates/vector.hpp +++ b/include/godot_cpp/templates/vector.hpp @@ -32,21 +32,25 @@ /** * @class Vector - * Vector container. Regular Vector Container. Use with care and for smaller arrays when possible. Use Vector for large arrays. + * Vector container. Simple copy-on-write container. + * + * LocalVector is an alternative available for internal use when COW is not + * required. */ #include #include #include -#include #include #include -#include #include namespace godot { +template +class Vector; + template class VectorWriteProxy { public: @@ -64,14 +68,15 @@ class Vector { public: VectorWriteProxy write; typedef typename CowData::Size Size; + typedef typename CowData::USize USize; private: 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 + _FORCE_INLINE_ bool push_back(T p_elem) { return _cowdata.push_back(std::move(p_elem)); } + _FORCE_INLINE_ bool append(T p_elem) { return _cowdata.push_back(std::move(p_elem)); } //alias void fill(T p_elem); void remove_at(Size p_index) { _cowdata.remove_at(p_index); } @@ -86,26 +91,82 @@ class Vector { void reverse(); - _FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); } - _FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); } - _FORCE_INLINE_ void clear() { resize(0); } - _FORCE_INLINE_ bool is_empty() const { return _cowdata.is_empty(); } + _FORCE_INLINE_ T *ptrw() _LIFETIME_BOUND_ { return _cowdata.ptrw(); } + _FORCE_INLINE_ const T *ptr() const _LIFETIME_BOUND_ { return _cowdata.ptr(); } + _FORCE_INLINE_ Size size() const { return _cowdata.size(); } + _FORCE_INLINE_ USize capacity() const { return _cowdata.capacity(); } + + _FORCE_INLINE_ operator Span() const _LIFETIME_BOUND_ { return _cowdata.span(); } + _FORCE_INLINE_ Span span() const _LIFETIME_BOUND_ { return _cowdata.span(); } - _FORCE_INLINE_ operator Span() const { return _cowdata.span(); } - _FORCE_INLINE_ Span span() const { return _cowdata.span(); } + _FORCE_INLINE_ void clear() { _cowdata.clear(); } + _FORCE_INLINE_ bool is_empty() const { return _cowdata.is_empty(); } - _FORCE_INLINE_ T get(Size p_index) { return _cowdata.get(p_index); } - _FORCE_INLINE_ const T &get(Size p_index) const { return _cowdata.get(p_index); } + _FORCE_INLINE_ T get(Size p_index) _LIFETIME_BOUND_ { return _cowdata.get(p_index); } + _FORCE_INLINE_ const T &get(Size p_index) const _LIFETIME_BOUND_ { return _cowdata.get(p_index); } _FORCE_INLINE_ void set(Size p_index, const T &p_elem) { _cowdata.set(p_index, p_elem); } - _FORCE_INLINE_ Size size() const { return _cowdata.size(); } - 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); } + + /// Resize the vector. + /// Elements are initialized (or not) depending on what the default C++ behavior for this type is. + _FORCE_INLINE_ Error resize(Size p_size) { + return _cowdata.template resize>(p_size); + } + + /// Resize and set new values to 0 / false / nullptr. + /// This is only available for zero constructible types. + _FORCE_INLINE_ Error resize_initialized(Size p_size) { + return _cowdata.template resize(p_size); + } + + /// Resize and keep memory uninitialized. + /// This means that any newly added elements have an unknown value, and are expected to be set after the `resize_uninitialized` call. + /// This is only available for trivially destructible types (otherwise, trivial resize might be UB). + _FORCE_INLINE_ Error resize_uninitialized(Size p_size) { + // resize() statically asserts that T is compatible, no need to do it ourselves. + return _cowdata.template resize(p_size); + } + + /// Reserves capacity for at least p_size total elements. + /// You can use `reserve` before repeated insertions to improve performance. + /// The capacity grows in 1.5x increments when possible, and uses `p_size` + /// exactly otherwise. + Error reserve(Size p_size) { + ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER); + return _cowdata.reserve(p_size); + } + + /// Reserves capacity for exactly p_size total elements. + /// This can be useful to reduce RAM use of large vectors, but wastes CPU + /// time if more than p_size elements are added after the `reserve_exact` call. + /// Prefer using `reserve`, unless the vector (or copies of it) will never + /// grow again after p_size elements are inserted. + Error reserve_exact(Size p_size) { + ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER); + return _cowdata.reserve_exact(p_size); + } + + _FORCE_INLINE_ const T &operator[](Size p_index) const _LIFETIME_BOUND_ { 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); } + Error insert(Size p_pos, T p_val) { return _cowdata.insert(p_pos, std::move(p_val)); } + Size find(const T &p_val, Size p_from = 0) const { + if (p_from < 0) { + p_from = size() + p_from; + } + if (p_from < 0 || p_from >= size()) { + return -1; + } + return span().find(p_val, p_from); + } + Size rfind(const T &p_val, Size p_from = -1) const { + if (p_from < 0) { + p_from = size() + p_from; + } + if (p_from < 0 || p_from >= size()) { + return -1; + } + return span().rfind(p_val, p_from); + } + Size count(const T &p_val) const { return span().count(p_val); } // Must take a copy instead of a reference (see GH-31736). void append_array(Vector p_other); @@ -113,7 +174,7 @@ class Vector { _FORCE_INLINE_ bool has(const T &p_val) const { return find(p_val) != -1; } void sort() { - sort_custom<_DefaultComparator>(); + sort_custom>(); } template @@ -129,16 +190,15 @@ class Vector { } Size bsearch(const T &p_value, bool p_before) const { - return bsearch_custom<_DefaultComparator>(p_value, p_before); + return bsearch_custom>(p_value, p_before); } template Size bsearch_custom(const Value &p_value, bool p_before, Args &&...args) const { - SearchArray search{ args... }; - return search.bisect(ptr(), size(), p_value, p_before); + return span().bisect(p_value, p_before, Comparator{ args... }); } - Vector duplicate() { + Vector duplicate() const { return *this; } @@ -152,7 +212,7 @@ class Vector { insert(i, p_val); } - void operator=(const Vector &p_from) { _cowdata._ref(p_from._cowdata); } + void operator=(const Vector &p_from) { _cowdata = p_from._cowdata; } void operator=(Vector &&p_from) { _cowdata = std::move(p_from._cowdata); } Vector to_byte_array() const { @@ -196,31 +256,8 @@ class Vector { return result; } - bool operator==(const Vector &p_arr) const { - Size s = size(); - if (s != p_arr.size()) { - return false; - } - for (Size i = 0; i < s; i++) { - if (operator[](i) != p_arr[i]) { - return false; - } - } - return true; - } - - bool operator!=(const Vector &p_arr) const { - Size s = size(); - if (s != p_arr.size()) { - return true; - } - for (Size i = 0; i < s; i++) { - if (operator[](i) != p_arr[i]) { - return true; - } - } - return false; - } + bool operator==(const Vector &p_arr) const { return span() == p_arr.span(); } + bool operator!=(const Vector &p_arr) const { return span() != p_arr.span(); } struct Iterator { _FORCE_INLINE_ T &operator*() const { @@ -272,34 +309,33 @@ class Vector { const T *elem_ptr = nullptr; }; - _FORCE_INLINE_ Iterator begin() { + _FORCE_INLINE_ Iterator begin() _LIFETIME_BOUND_ { return Iterator(ptrw()); } - _FORCE_INLINE_ Iterator end() { + _FORCE_INLINE_ Iterator end() _LIFETIME_BOUND_ { return Iterator(ptrw() + size()); } - _FORCE_INLINE_ ConstIterator begin() const { + _FORCE_INLINE_ ConstIterator begin() const _LIFETIME_BOUND_ { return ConstIterator(ptr()); } - _FORCE_INLINE_ ConstIterator end() const { + _FORCE_INLINE_ ConstIterator end() const _LIFETIME_BOUND_ { return ConstIterator(ptr() + size()); } _FORCE_INLINE_ Vector() {} _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() {} + _FORCE_INLINE_ explicit Vector(Span p_span) : + _cowdata(p_span) {} + _FORCE_INLINE_ Vector(const Vector &p_from) = default; + _FORCE_INLINE_ Vector(Vector &&p_from) = default; }; template void Vector::reverse() { + T *p = ptrw(); for (Size i = 0; i < size() / 2; i++) { - T *p = ptrw(); SWAP(p[i], p[size() - i - 1]); } } @@ -312,20 +348,12 @@ void Vector::append_array(Vector p_other) { } const Size bs = size(); resize(bs + ds); + T *p = ptrw(); for (Size i = 0; i < ds; ++i) { - ptrw()[bs + i] = p_other[i]; + p[bs + i] = p_other[i]; } } -template -bool Vector::push_back(T p_elem) { - Error err = resize(size() + 1); - ERR_FAIL_COND_V(err, true); - set(size() - 1, p_elem); - - return false; -} - template void Vector::fill(T p_elem) { T *p = ptrw(); @@ -334,4 +362,8 @@ void Vector::fill(T p_elem) { } } +// Zero-constructing Vector initializes CowData.ptr() to nullptr and thus empty. +template +struct is_zero_constructible> : std::true_type {}; + } // namespace godot diff --git a/include/godot_cpp/templates/vmap.hpp b/include/godot_cpp/templates/vmap.hpp deleted file mode 100644 index ea043d9fc..000000000 --- a/include/godot_cpp/templates/vmap.hpp +++ /dev/null @@ -1,206 +0,0 @@ -/**************************************************************************/ -/* vmap.hpp */ -/**************************************************************************/ -/* 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 VMap { -public: - struct Pair { - T key; - V value; - - _FORCE_INLINE_ Pair() {} - - _FORCE_INLINE_ Pair(const T &p_key, const V &p_value) { - key = p_key; - value = p_value; - } - }; - -private: - CowData _cowdata; - - _FORCE_INLINE_ int _find(const T &p_val, bool &r_exact) const { - r_exact = false; - if (_cowdata.is_empty()) { - return 0; - } - - int low = 0; - int high = _cowdata.size() - 1; - const Pair *a = _cowdata.ptr(); - int middle = 0; - -#ifdef DEBUG_ENABLED - if (low > high) { - ERR_PRINT("low > high, this may be a bug"); - } -#endif - while (low <= high) { - middle = (low + high) / 2; - - if (p_val < a[middle].key) { - high = middle - 1; //search low end of array - } else if (a[middle].key < p_val) { - low = middle + 1; //search high end of array - } else { - r_exact = true; - return middle; - } - } - - //return the position where this would be inserted - if (a[middle].key < p_val) { - middle++; - } - return middle; - } - - _FORCE_INLINE_ int _find_exact(const T &p_val) const { - if (_cowdata.is_empty()) { - return -1; - } - - int low = 0; - int high = _cowdata.size() - 1; - int middle; - const Pair *a = _cowdata.ptr(); - - while (low <= high) { - middle = (low + high) / 2; - - if (p_val < a[middle].key) { - high = middle - 1; //search low end of array - } else if (a[middle].key < p_val) { - low = middle + 1; //search high end of array - } else { - return middle; - } - } - - return -1; - } - -public: - int insert(const T &p_key, const V &p_val) { - bool exact; - int pos = _find(p_key, exact); - if (exact) { - _cowdata.get_m(pos).value = p_val; - return pos; - } - _cowdata.insert(pos, Pair(p_key, p_val)); - return pos; - } - - bool has(const T &p_val) const { - return _find_exact(p_val) != -1; - } - - void erase(const T &p_val) { - int pos = _find_exact(p_val); - if (pos < 0) { - return; - } - _cowdata.remove_at(pos); - } - - int find(const T &p_val) const { - return _find_exact(p_val); - } - - int find_nearest(const T &p_val) const { - if (_cowdata.is_empty()) { - return -1; - } - bool exact; - return _find(p_val, exact); - } - - _FORCE_INLINE_ int size() const { return _cowdata.size(); } - _FORCE_INLINE_ bool is_empty() const { return _cowdata.is_empty(); } - - const Pair *get_array() const { - return _cowdata.ptr(); - } - - Pair *get_array() { - return _cowdata.ptrw(); - } - - const V &getv(int p_index) const { - return _cowdata.get(p_index).value; - } - - V &getv(int p_index) { - return _cowdata.get_m(p_index).value; - } - - const T &getk(int p_index) const { - return _cowdata.get(p_index).key; - } - - T &getk(int p_index) { - return _cowdata.get_m(p_index).key; - } - - inline const V &operator[](const T &p_key) const { - int pos = _find_exact(p_key); - - CRASH_COND(pos < 0); - - return _cowdata.get(pos).value; - } - - inline V &operator[](const T &p_key) { - int pos = _find_exact(p_key); - if (pos < 0) { - pos = insert(p_key, V()); - } - - return _cowdata.get_m(pos).value; - } - - _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) { - _cowdata._ref(p_from._cowdata); - } -}; - -} // namespace godot diff --git a/include/godot_cpp/variant/char_string.hpp b/include/godot_cpp/variant/char_string.hpp index 908dc3432..ec379239d 100644 --- a/include/godot_cpp/variant/char_string.hpp +++ b/include/godot_cpp/variant/char_string.hpp @@ -41,15 +41,15 @@ template class CharStringT; template -class CharProxy { - template - friend class CharStringT; +class [[nodiscard]] CharProxy { + friend String; + friend CharStringT; - const int64_t _index; + const int _index; CowData &_cowdata; - static inline const T _null = 0; + static constexpr T _null = 0; - _FORCE_INLINE_ CharProxy(const int64_t &p_index, CowData &p_cowdata) : + _FORCE_INLINE_ CharProxy(const int &p_index, CowData &p_cowdata) : _index(p_index), _cowdata(p_cowdata) {} @@ -79,61 +79,100 @@ class CharProxy { } }; -template -class CharStringT { - friend class String; +/*************************************************************************/ +/* CharStringT */ +/*************************************************************************/ +template +class [[nodiscard]] CharStringT { CowData _cowdata; - static inline const T _null = 0; + static constexpr T _null = 0; public: - _FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); } - _FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); } - _FORCE_INLINE_ int64_t size() const { return _cowdata.size(); } - Error resize(int64_t p_size) { return _cowdata.resize(p_size); } - - _FORCE_INLINE_ T get(int64_t p_index) const { return _cowdata.get(p_index); } - _FORCE_INLINE_ void set(int64_t p_index, const T &p_elem) { _cowdata.set(p_index, p_elem); } - _FORCE_INLINE_ const T &operator[](int64_t p_index) const { + _FORCE_INLINE_ T *ptrw() _LIFETIME_BOUND_ { return _cowdata.ptrw(); } + _FORCE_INLINE_ const T *ptr() const _LIFETIME_BOUND_ { return _cowdata.ptr(); } + _FORCE_INLINE_ const T *get_data() const _LIFETIME_BOUND_ { return size() ? ptr() : &_null; } + + // Returns the number of characters in the buffer, including the terminating NUL character. + // In most cases, length() should be used instead. + _FORCE_INLINE_ int size() const { return _cowdata.size(); } + // Returns the number of characters in the string (excluding terminating NUL character). + _FORCE_INLINE_ int length() const { return size() ? size() - 1 : 0; } + _FORCE_INLINE_ bool is_empty() const { return length() == 0; } + + _FORCE_INLINE_ operator Span() const _LIFETIME_BOUND_ { return Span(ptr(), length()); } + _FORCE_INLINE_ Span span() const _LIFETIME_BOUND_ { return Span(ptr(), length()); } + + /// Resizes the string. The given size must include the null terminator. + /// New characters are not initialized, and should be set by the caller. + _FORCE_INLINE_ Error resize_uninitialized(int64_t p_size) { return _cowdata.template resize(p_size); } + + _FORCE_INLINE_ T get(int p_index) const { return _cowdata.get(p_index); } + _FORCE_INLINE_ void set(int p_index, const T &p_elem) { _cowdata.set(p_index, p_elem); } + _FORCE_INLINE_ const T &operator[](int p_index) const { if (unlikely(p_index == _cowdata.size())) { return _null; } - return _cowdata.get(p_index); } - _FORCE_INLINE_ CharProxy operator[](int64_t p_index) { return CharProxy(p_index, _cowdata); } + _FORCE_INLINE_ CharProxy operator[](int p_index) { return CharProxy(p_index, _cowdata); } - _FORCE_INLINE_ CharStringT() {} - _FORCE_INLINE_ CharStringT(const CharStringT &p_str) { _cowdata._ref(p_str._cowdata); } - _FORCE_INLINE_ void operator=(const CharStringT &p_str) { _cowdata._ref(p_str._cowdata); } + _FORCE_INLINE_ CharStringT() = default; + _FORCE_INLINE_ CharStringT(const CharStringT &p_str) = default; + _FORCE_INLINE_ CharStringT(CharStringT &&p_str) = default; + _FORCE_INLINE_ void operator=(const CharStringT &p_str) { _cowdata = p_str._cowdata; } + _FORCE_INLINE_ void operator=(CharStringT &&p_str) { _cowdata = std::move(p_str._cowdata); } _FORCE_INLINE_ CharStringT(const T *p_cstr) { copy_from(p_cstr); } + _FORCE_INLINE_ void operator=(const T *p_cstr) { copy_from(p_cstr); } + + _FORCE_INLINE_ bool operator==(const CharStringT &p_other) const { return span() == p_other.span(); } + _FORCE_INLINE_ bool operator!=(const CharStringT &p_other) const { return !(*this == p_other); } + _FORCE_INLINE_ bool operator<(const CharStringT &p_other) const { + if (length() == 0) { + return p_other.length() != 0; + } + return str_compare(get_data(), p_other.get_data()) < 0; + } + _FORCE_INLINE_ CharStringT &operator+=(T p_char) { + const int lhs_len = length(); + resize_uninitialized(lhs_len + 2); + + T *dst = ptrw(); + dst[lhs_len] = p_char; + dst[lhs_len + 1] = _null; + + return *this; + } - void operator=(const T *p_cstr); - bool operator<(const CharStringT &p_right) const; - CharStringT &operator+=(T p_char); - int64_t length() const { return size() ? size() - 1 : 0; } - const T *get_data() const; - operator const T *() const { return get_data(); } + uint32_t hash() const { return hash_djb2(get_data()); } protected: - void copy_from(const T *p_cstr); -}; + void copy_from(const T *p_cstr) { + if (!p_cstr) { + resize_uninitialized(0); + return; + } + + size_t len = strlen(p_cstr); + if (len == 0) { + resize_uninitialized(0); + return; + } -template <> -const char *CharStringT::get_data() const; + Error err = resize_uninitialized(++len); // include terminating null char. -template <> -const char16_t *CharStringT::get_data() const; + ERR_FAIL_COND_MSG(err != OK, "Failed to copy C-string."); -template <> -const char32_t *CharStringT::get_data() const; + memcpy(ptrw(), p_cstr, len * sizeof(T)); + } +}; -template <> -const wchar_t *CharStringT::get_data() const; +template +struct is_zero_constructible> : std::true_type {}; -typedef CharStringT CharString; -typedef CharStringT Char16String; -typedef CharStringT Char32String; -typedef CharStringT CharWideString; +using CharString = CharStringT; +using Char16String = CharStringT; +using Char32String = CharStringT; +using CharWideString = CharStringT; } // namespace godot diff --git a/src/variant/char_string.cpp b/src/variant/char_string.cpp index 42fe0bab4..38de37ee2 100644 --- a/src/variant/char_string.cpp +++ b/src/variant/char_string.cpp @@ -65,94 +65,6 @@ _FORCE_INLINE_ bool is_str_less(const L *l_ptr, const R *r_ptr) { } } -template -bool CharStringT::operator<(const CharStringT &p_right) const { - if (length() == 0) { - return p_right.length() != 0; - } - - return is_str_less(get_data(), p_right.get_data()); -} - -template -CharStringT &CharStringT::operator+=(T p_char) { - const int64_t lhs_len = length(); - resize(lhs_len + 2); - - T *dst = ptrw(); - dst[lhs_len] = p_char; - dst[lhs_len + 1] = 0; - - return *this; -} - -template -void CharStringT::operator=(const T *p_cstr) { - copy_from(p_cstr); -} - -template <> -const char *CharStringT::get_data() const { - if (size()) { - return &operator[](0); - } else { - return ""; - } -} - -template <> -const char16_t *CharStringT::get_data() const { - if (size()) { - return &operator[](0); - } else { - return u""; - } -} - -template <> -const char32_t *CharStringT::get_data() const { - if (size()) { - return &operator[](0); - } else { - return U""; - } -} - -template <> -const wchar_t *CharStringT::get_data() const { - if (size()) { - return &operator[](0); - } else { - return L""; - } -} - -template -void CharStringT::copy_from(const T *p_cstr) { - if (!p_cstr) { - resize(0); - return; - } - - size_t len = std::char_traits::length(p_cstr); - - if (len == 0) { - resize(0); - return; - } - - Error err = resize(++len); // include terminating null char - - ERR_FAIL_COND_MSG(err != OK, "Failed to copy C-string."); - - memcpy(ptrw(), p_cstr, len); -} - -template class CharStringT; -template class CharStringT; -template class CharStringT; -template class CharStringT; - // Custom String functions that are not part of bound API. // It's easier to have them written in C++ directly than in a Python script that generates them. @@ -233,7 +145,7 @@ CharString String::utf8() const { int64_t length = ::godot::gdextension_interface::string_to_utf8_chars(_native_ptr(), nullptr, 0); int64_t size = length + 1; CharString str; - str.resize(size); + str.resize_uninitialized(size); ::godot::gdextension_interface::string_to_utf8_chars(_native_ptr(), str.ptrw(), length); str[length] = '\0'; @@ -245,7 +157,7 @@ CharString String::ascii() const { int64_t length = ::godot::gdextension_interface::string_to_latin1_chars(_native_ptr(), nullptr, 0); int64_t size = length + 1; CharString str; - str.resize(size); + str.resize_uninitialized(size); ::godot::gdextension_interface::string_to_latin1_chars(_native_ptr(), str.ptrw(), length); str[length] = '\0'; @@ -257,7 +169,7 @@ Char16String String::utf16() const { int64_t length = ::godot::gdextension_interface::string_to_utf16_chars(_native_ptr(), nullptr, 0); int64_t size = length + 1; Char16String str; - str.resize(size); + str.resize_uninitialized(size); ::godot::gdextension_interface::string_to_utf16_chars(_native_ptr(), str.ptrw(), length); str[length] = '\0'; @@ -269,7 +181,7 @@ Char32String String::utf32() const { int64_t length = ::godot::gdextension_interface::string_to_utf32_chars(_native_ptr(), nullptr, 0); int64_t size = length + 1; Char32String str; - str.resize(size); + str.resize_uninitialized(size); ::godot::gdextension_interface::string_to_utf32_chars(_native_ptr(), str.ptrw(), length); str[length] = '\0'; @@ -281,7 +193,7 @@ CharWideString String::wide_string() const { int64_t length = ::godot::gdextension_interface::string_to_wide_chars(_native_ptr(), nullptr, 0); int64_t size = length + 1; CharWideString str; - str.resize(size); + str.resize_uninitialized(size); ::godot::gdextension_interface::string_to_wide_chars(_native_ptr(), str.ptrw(), length); str[length] = '\0'; diff --git a/test/src/tests.h b/test/src/tests.h index a2c39be03..6d2f3a5ea 100644 --- a/test/src/tests.h +++ b/test/src/tests.h @@ -22,5 +22,4 @@ #include #include #include -#include #include