diff --git a/core/os/memory.h b/core/os/memory.h index cda01333fd3..1a7dc288706 100644 --- a/core/os/memory.h +++ b/core/os/memory.h @@ -220,6 +220,28 @@ _FORCE_INLINE_ void memnew_arr_placement(T *p_start, size_t p_num) { } } +// 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(); + } + } +} + /** * Wonders of having own array functions, you can actually check the length of * an allocated-with memnew_arr() array diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 0cb4b2c414a..2c3934a7c19 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -1253,7 +1253,7 @@ Vector String::split_floats_mk(const Vector &p_splitters, bool p_ String buffer = *this; while (true) { - int idx; + int idx = 0; int end = findmk(p_splitters, from, &idx); int spl_len = 1; if (end < 0) { @@ -1308,7 +1308,7 @@ Vector String::split_ints_mk(const Vector &p_splitters, bool p_allo int len = length(); while (true) { - int idx; + int idx = 0; int end = findmk(p_splitters, from, &idx); int spl_len = 1; if (end < 0) { diff --git a/core/string/ustring.h b/core/string/ustring.h index 909aaa746b1..4ef5234fa1f 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -177,10 +177,10 @@ class [[nodiscard]] CharStringT { public: _FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); } _FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); } - _FORCE_INLINE_ const T *get_data() const { return ptr() ? ptr() : &_null; } + _FORCE_INLINE_ const T *get_data() const { return size() ? ptr() : &_null; } _FORCE_INLINE_ int size() const { return _cowdata.size(); } - _FORCE_INLINE_ int length() const { return ptr() ? size() - 1 : 0; } + _FORCE_INLINE_ int length() const { return size() ? size() - 1 : 0; } _FORCE_INLINE_ bool is_empty() const { return length() == 0; } _FORCE_INLINE_ operator Span() const { return Span(ptr(), length()); } @@ -297,10 +297,10 @@ public: _FORCE_INLINE_ char32_t *ptrw() { return _cowdata.ptrw(); } _FORCE_INLINE_ const char32_t *ptr() const { return _cowdata.ptr(); } - _FORCE_INLINE_ const char32_t *get_data() const { return ptr() ? ptr() : &_null; } + _FORCE_INLINE_ const char32_t *get_data() const { return size() ? ptr() : &_null; } _FORCE_INLINE_ int size() const { return _cowdata.size(); } - _FORCE_INLINE_ int length() const { return ptr() ? size() - 1 : 0; } + _FORCE_INLINE_ int length() const { return size() ? size() - 1 : 0; } _FORCE_INLINE_ bool is_empty() const { return length() == 0; } _FORCE_INLINE_ operator Span() const { return Span(ptr(), length()); } @@ -317,6 +317,11 @@ public: /// New characters are not initialized, and should be set by the caller. Error resize_uninitialized(int64_t p_size) { return _cowdata.resize(p_size); } + Error reserve(int64_t p_size) { + ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER); + return _cowdata.reserve(p_size); + } + _FORCE_INLINE_ const char32_t &operator[](int p_index) const { if (unlikely(p_index == _cowdata.size())) { return _null; diff --git a/core/templates/cowdata.h b/core/templates/cowdata.h index 918420dba78..c0894520840 100644 --- a/core/templates/cowdata.h +++ b/core/templates/cowdata.h @@ -40,9 +40,16 @@ static_assert(std::is_trivially_destructible_v>); +// 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 { @@ -52,15 +59,16 @@ public: static constexpr USize MAX_INT = INT64_MAX; private: - // Alignment: ↓ max_align_t ↓ USize ↓ max_align_t - // ┌────────────────────┬──┬─────────────┬──┬───────────... - // │ SafeNumeric │░░│ USize │░░│ T[] - // │ ref. count │░░│ data size │░░│ data - // └────────────────────┴──┴─────────────┴──┴───────────... - // Offset: ↑ REF_COUNT_OFFSET ↑ SIZE_OFFSET ↑ DATA_OFFSET + // Alignment: ↓ max_align_t ↓ USize ↓ USize ↓ max_align_t + // ┌────────────────────┬──┬───────────────┬──┬─────────────┬──┬───────────... + // │ 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 = Memory::get_aligned_address(REF_COUNT_OFFSET + sizeof(SafeNumeric), alignof(USize)); + 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), alignof(max_align_t)); mutable T *_ptr = nullptr; @@ -71,22 +79,21 @@ private: 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); } + /// Note: Assumes _ptr != nullptr. + _FORCE_INLINE_ USize *_get_capacity() const { + return (USize *)((uint8_t *)_ptr - DATA_OFFSET + CAPACITY_OFFSET); + } + _FORCE_INLINE_ static USize _get_alloc_size(USize p_elements) { return next_power_of_2(p_elements * (USize)sizeof(T)); } @@ -121,22 +128,29 @@ private: void _ref(const CowData *p_from); void _ref(const CowData &p_from); - // Ensures that the backing buffer is at least p_size wide, and that this CowData instance is - // the only reference to it. The buffer is populated with as many element copies from the old - // array as possible. - // It is the responsibility of the caller to populate newly allocated space up to p_size. - Error _fork_allocate(USize p_size); - Error _copy_on_write() { return _fork_allocate(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(USize p_capacity); - // Allocates a backing array of the given capacity. The reference count is initialized to 1. - // It is the responsibility of the caller to populate the array and the new size property. - Error _alloc(USize p_alloc_size); + /// 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(USize p_capacity); + Error _realloc_bytes(USize p_bytes); - // Re-allocates the backing array to the given capacity. The reference count is initialized to 1. - // It is the responsibility of the caller to populate the array and the new size property. - // The caller must also make sure there are no other references to the data, as pointers may - // be invalidated. - Error _realloc(USize p_alloc_size); + /// 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_min_capacity is enough to hold all elements. + [[nodiscard]] Error _copy_to_new_buffer(USize p_min_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); } @@ -151,7 +165,8 @@ public: } _FORCE_INLINE_ T *ptrw() { - _copy_on_write(); + // If forking fails, we can only crash. + CRASH_COND(_copy_on_write()); return _ptr; } @@ -159,27 +174,25 @@ public: 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() { _unref(); } - _FORCE_INLINE_ bool is_empty() const { return _ptr == nullptr; } + _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) { 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]; } @@ -189,33 +202,15 @@ public: 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]); - } + Error reserve(USize p_min_capacity); - resize(len - 1); - } + _FORCE_INLINE_ void remove_at(Size p_index); - 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; - - return OK; - } + Error insert(Size p_pos, const T &p_val); + Error push_back(const T &p_val); _FORCE_INLINE_ operator Span() const { return Span(ptr(), size()); } _FORCE_INLINE_ Span span() const { return operator Span(); } @@ -236,8 +231,7 @@ 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; @@ -251,17 +245,16 @@ 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(); - } - } + destruct_arr_placement(prev_ptr, current_size); - // Free memory. + // Safety check; none of the destructors should have added elements during destruction. + DEV_ASSERT(!_ptr); + + // Free Memory. Memory::free_static((uint8_t *)prev_ptr - DATA_OFFSET, false); #ifdef DEBUG_ENABLED @@ -272,86 +265,133 @@ void CowData::_unref() { } template -Error CowData::_fork_allocate(USize p_size) { - if (p_size == 0) { - // Wants to clean up. +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 OK; + return; } - USize alloc_size; - ERR_FAIL_COND_V(!_get_alloc_size_checked(p_size, &alloc_size), ERR_OUT_OF_MEMORY); + const USize new_size = prev_size - 1; - const USize prev_size = size(); + if (_get_refcount()->get() == 1) { + // We're the only owner; remove in-place. + + // 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)); + + // Shrink buffer if necessary. + const USize new_alloc_size = _get_alloc_size(new_size); + const USize prev_alloc_size = _get_alloc_size(capacity()); + if (new_alloc_size < prev_alloc_size) { + Error err = _realloc_bytes(new_alloc_size); + CRASH_COND(err); + } + + *_get_size() = new_size; + } else { + // Remove by forking. + Error err = _copy_to_new_buffer(new_size, p_index, 0, new_size - p_index); + CRASH_COND(err); + } +} + +template +Error CowData::insert(Size p_pos, const T &p_val) { + const Size new_size = size() + 1; + ERR_FAIL_INDEX_V(p_pos, new_size, ERR_INVALID_PARAMETER); if (!_ptr) { - // We had no data before; just allocate a new array. - const Error error = _alloc(alloc_size); - if (error) { - return error; - } + _alloc(1); + *_get_size() = 1; } else if (_get_refcount()->get() == 1) { - // Resize in-place. - // NOTE: This case is not just an optimization, but required, as some callers depend on - // `_copy_on_write()` calls not changing the pointer after the first fork - // (e.g. mutable iterators). - if (p_size == prev_size) { - // We can shortcut here; we don't need to do anything. - return OK; - } - - // Destroy extraneous elements. - if constexpr (!std::is_trivially_destructible_v) { - for (USize i = prev_size; i > p_size; i--) { - _ptr[i - 1].~T(); - } - } - - if (alloc_size != _get_alloc_size(prev_size)) { - const Error error = _realloc(alloc_size); + if ((USize)new_size > capacity()) { + // Need to grow. + const Error error = _realloc(new_size); if (error) { - // Out of memory; the current array is still valid though. return error; } } + + // 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 { - // Resize by forking. - - // 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; - - const Error error = _alloc(alloc_size); + // 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 = MAX(capacity(), (USize)new_size); + const Error error = _copy_to_new_buffer(new_capacity, p_pos, 1, size() - p_pos); if (error) { - // On failure to allocate, just give up the old data and return. - // We could recover our old pointer from prev_data, but by just dropping our data, we - // consciously invite early failure for the case that the caller does not handle this - // case gracefully. return error; } - - // Copy over elements. - const USize copied_element_count = MIN(prev_size, p_size); - if (copied_element_count > 0) { - if constexpr (std::is_trivially_copyable_v) { - memcpy((uint8_t *)_ptr, (uint8_t *)prev_data._ptr, copied_element_count * sizeof(T)); - } else { - for (USize i = 0; i < copied_element_count; i++) { - memnew_placement(&_ptr[i], T(prev_data._ptr[i])); - } - } - } } - // Set our new size. - *_get_size() = p_size; + // Create the new element at the given index. + memnew_placement(_ptr + p_pos, T(p_val)); return OK; } +template +Error CowData::push_back(const T &p_val) { + const Size new_size = size() + 1; + + if (!_ptr) { + // Grow by allocating. + _alloc(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(new_size); + if (error) { + return error; + } + } + + *_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 = MAX(capacity(), (USize)new_size); + const Error error = _copy_to_new_buffer(new_capacity, size(), 1, 0); + if (error) { + return error; + } + } + + // Create the new element at the given index. + memnew_placement(_ptr + new_size - 1, T(p_val)); + + return OK; +} + +template +Error CowData::reserve(USize p_min_capacity) { + ERR_FAIL_COND_V_MSG(p_min_capacity < (USize)size(), ERR_INVALID_PARAMETER, "reserve() called with a capacity smaller than the current size. This is likely a mistake."); + + if (p_min_capacity <= capacity()) { + // No need to reserve more, we already have (at least) the right size. + return OK; + } + + if (!_ptr) { + // Initial allocation. + return _alloc(p_min_capacity); + } else if (_get_refcount()->get() == 1) { + // Grow in-place. + return _realloc(p_min_capacity); + } else { + // Grow by forking. + return _copy_to_new_buffer(p_min_capacity, size(), 0, 0); + } +} + template template Error CowData::resize(Size p_size) { @@ -359,41 +399,102 @@ Error CowData::resize(Size p_size) { const Size prev_size = size(); if (p_size == prev_size) { + // Caller wants to stay the same size, so we do nothing. return OK; } - const Error error = _fork_allocate(p_size); - if (error) { - return error; - } + if (p_size > prev_size) { + // Caller wants to grow. - if constexpr (p_initialize) { - if (p_size > prev_size) { + if (!_ptr) { + // Grow by allocating. + const Error error = _alloc(p_size); + if (error) { + return error; + } + } else if (_get_refcount()->get() == 1) { + // Grow in-place. + if ((USize)p_size > capacity()) { + const Error error = _realloc(p_size); + if (error) { + return error; + } + } + } else { + // Grow by forking. + const Error error = _copy_to_new_buffer(p_size, prev_size, 0, 0); + if (error) { + return error; + } + } + + // Construct new elements. + if constexpr (p_initialize) { memnew_arr_placement(_ptr + prev_size, p_size - prev_size); } - } else { - static_assert(std::is_trivially_destructible_v); - } + *_get_size() = p_size; - return OK; + 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_alloc_size = _get_alloc_size(p_size); + const USize prev_alloc_size = _get_alloc_size(capacity()); + if (new_alloc_size < prev_alloc_size) { + Error err = _realloc_bytes(new_alloc_size); + CRASH_COND(err); + } + + *_get_size() = p_size; + return OK; + } else { + // Shrink by forking. + return _copy_to_new_buffer(p_size, p_size, 0, 0); + } + } } template -Error CowData::_alloc(USize p_alloc_size) { - uint8_t *mem_new = (uint8_t *)Memory::alloc_static(p_alloc_size + DATA_OFFSET, false); +Error CowData::_alloc(USize p_min_capacity) { + DEV_ASSERT(!_ptr); + + USize alloc_size; + ERR_FAIL_COND_V(!_get_alloc_size_checked(p_min_capacity, &alloc_size), ERR_OUT_OF_MEMORY); + + uint8_t *mem_new = (uint8_t *)Memory::alloc_static(alloc_size + DATA_OFFSET, false); ERR_FAIL_NULL_V(mem_new, ERR_OUT_OF_MEMORY); _ptr = _get_data_ptr(mem_new); // 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() = alloc_size / sizeof(T); return OK; } template -Error CowData::_realloc(USize 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::_realloc(USize p_min_capacity) { + USize bytes; + ERR_FAIL_COND_V(!_get_alloc_size_checked(p_min_capacity, &bytes), ERR_OUT_OF_MEMORY); + return _realloc_bytes(bytes); +} + +template +Error CowData::_realloc_bytes(USize p_bytes) { + DEV_ASSERT(_ptr); + + uint8_t *mem_new = (uint8_t *)Memory::realloc_static(((uint8_t *)_ptr) - DATA_OFFSET, p_bytes + DATA_OFFSET, false); ERR_FAIL_NULL_V(mem_new, ERR_OUT_OF_MEMORY); _ptr = _get_data_ptr(mem_new); @@ -401,10 +502,55 @@ Error CowData::_realloc(USize p_alloc_size) { // 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_bytes / sizeof(T); return OK; } +template +Error CowData::_copy_to_new_buffer(USize p_min_capacity, USize p_size_from_start, USize p_gap, USize p_size_from_back) { + DEV_ASSERT(p_min_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; + + const Error error = _alloc(p_min_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; + } + + // 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 +Error CowData::_copy_on_write() { + if (!_ptr || _get_refcount()->get() == 1) { + // Nothing to do. + return OK; + } + + // Fork to become the only reference. + return _copy_to_new_buffer(capacity(), size(), 0, 0); +} + template void CowData::_ref(const CowData *p_from) { _ref(*p_from); @@ -429,15 +575,10 @@ 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(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(); } GODOT_GCC_WARNING_POP diff --git a/core/templates/local_vector.h b/core/templates/local_vector.h index 197d5991338..e437b819321 100644 --- a/core/templates/local_vector.h +++ b/core/templates/local_vector.h @@ -323,13 +323,7 @@ public: ret.resize(count); T *w = ret.ptrw(); 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]; - } - } + copy_arr_placement(w, data, count); } return ret; } diff --git a/core/templates/vector.h b/core/templates/vector.h index 631564d7109..0a36845f3f3 100644 --- a/core/templates/vector.h +++ b/core/templates/vector.h @@ -64,13 +64,14 @@ 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 push_back(T p_elem) { return _cowdata.push_back(p_elem); } _FORCE_INLINE_ bool append(const T &p_elem) { return push_back(p_elem); } //alias void fill(T p_elem); @@ -89,6 +90,7 @@ public: _FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); } _FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); } _FORCE_INLINE_ Size size() const { return _cowdata.size(); } + _FORCE_INLINE_ USize capacity() const { return _cowdata.capacity(); } _FORCE_INLINE_ operator Span() const { return _cowdata.span(); } _FORCE_INLINE_ Span span() const { return _cowdata.span(); } @@ -119,6 +121,11 @@ public: return _cowdata.template resize(p_size); } + Error reserve(Size p_size) { + ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER); + return _cowdata.reserve(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); } @@ -351,15 +358,6 @@ void Vector::append_array(Vector p_other) { } } -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(); diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 8d892422df8..bd84aaa6890 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -313,6 +313,11 @@ Error Array::resize(int p_new_size) { return err; } +Error Array::reserve(int p_new_size) { + ERR_FAIL_COND_V_MSG(_p->read_only, ERR_LOCKED, "Array is in read-only state."); + return _p->array.reserve(p_new_size); +} + Error Array::insert(int p_pos, const Variant &p_value) { ERR_FAIL_COND_V_MSG(_p->read_only, ERR_LOCKED, "Array is in read-only state."); Variant value = p_value; diff --git a/core/variant/array.h b/core/variant/array.h index 2f051227483..383d1333408 100644 --- a/core/variant/array.h +++ b/core/variant/array.h @@ -135,6 +135,7 @@ public: _FORCE_INLINE_ void append(const Variant &p_value) { push_back(p_value); } //for python compatibility void append_array(const Array &p_array); Error resize(int p_new_size); + Error reserve(int p_new_size); Error insert(int p_pos, const Variant &p_value); void remove_at(int p_pos); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs index cb180f9a8b3..3584ba65e6a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs @@ -538,7 +538,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != IntPtr.Zero ? (int)(*((ulong*)_ptr - 1)) : 0; + get => (int)NativeFuncs.godotsharp_string_size(in this); } } @@ -764,12 +764,6 @@ namespace Godot.NativeInterop // There are more fields here, but we don't care as we never store this in C# - public readonly int Size - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _arrayVector.Size; - } - public readonly unsafe bool IsReadOnly { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -782,12 +776,6 @@ namespace Godot.NativeInterop { private IntPtr _writeProxy; public unsafe godot_variant* _ptr; - - public readonly unsafe int Size - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; - } } public readonly unsafe godot_variant* Elements @@ -805,7 +793,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _p != null ? _p->Size : 0; + get => (int)NativeFuncs.godotsharp_array_size(in this); } public readonly unsafe bool IsReadOnly @@ -936,7 +924,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; + get => (int)NativeFuncs.godotsharp_packed_byte_array_size(in this); } } @@ -967,7 +955,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; + get => (int)NativeFuncs.godotsharp_packed_int32_array_size(in this); } } @@ -998,7 +986,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; + get => (int)NativeFuncs.godotsharp_packed_int64_array_size(in this); } } @@ -1029,7 +1017,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; + get => (int)NativeFuncs.godotsharp_packed_float32_array_size(in this); } } @@ -1060,7 +1048,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; + get => (int)NativeFuncs.godotsharp_packed_float64_array_size(in this); } } @@ -1091,7 +1079,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; + get => (int)NativeFuncs.godotsharp_packed_string_array_size(in this); } } @@ -1122,7 +1110,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; + get => (int)NativeFuncs.godotsharp_packed_vector2_array_size(in this); } } @@ -1153,7 +1141,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; + get => (int)NativeFuncs.godotsharp_packed_vector3_array_size(in this); } } @@ -1185,7 +1173,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; + get => (int)NativeFuncs.godotsharp_packed_vector4_array_size(in this); } } @@ -1217,7 +1205,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; + get => (int)NativeFuncs.godotsharp_packed_color_array_size(in this); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index 25b343c8ef9..48150535486 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -606,5 +606,31 @@ namespace Godot.NativeInterop // Object public static partial void godotsharp_object_to_string(IntPtr ptr, out godot_string r_str); + + // Vector + + public static partial long godotsharp_string_size(in godot_string p_self); + + public static partial long godotsharp_packed_byte_array_size(in godot_packed_byte_array p_self); + + public static partial long godotsharp_packed_int32_array_size(in godot_packed_int32_array p_self); + + public static partial long godotsharp_packed_int64_array_size(in godot_packed_int64_array p_self); + + public static partial long godotsharp_packed_float32_array_size(in godot_packed_float32_array p_self); + + public static partial long godotsharp_packed_float64_array_size(in godot_packed_float64_array p_self); + + public static partial long godotsharp_packed_string_array_size(in godot_packed_string_array p_self); + + public static partial long godotsharp_packed_vector2_array_size(in godot_packed_vector2_array p_self); + + public static partial long godotsharp_packed_vector3_array_size(in godot_packed_vector3_array p_self); + + public static partial long godotsharp_packed_vector4_array_size(in godot_packed_vector4_array p_self); + + public static partial long godotsharp_packed_color_array_size(in godot_packed_color_array p_self); + + public static partial long godotsharp_array_size(in godot_array p_self); } } diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 65ad599cd2c..b1f52e15306 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -1568,6 +1568,54 @@ void godotsharp_object_to_string(Object *p_ptr, godot_string *r_str) { } #endif +int64_t godotsharp_string_size(const String *p_self) { + return p_self->size(); +} + +int64_t godotsharp_packed_byte_array_size(const PackedByteArray *p_self) { + return p_self->size(); +} + +int64_t godotsharp_packed_int32_array_size(const PackedInt32Array *p_self) { + return p_self->size(); +} + +int64_t godotsharp_packed_int64_array_size(const PackedInt64Array *p_self) { + return p_self->size(); +} + +int64_t godotsharp_packed_float32_array_size(const PackedFloat32Array *p_self) { + return p_self->size(); +} + +int64_t godotsharp_packed_float64_array_size(const PackedFloat64Array *p_self) { + return p_self->size(); +} + +int64_t godotsharp_packed_string_array_size(const PackedStringArray *p_self) { + return p_self->size(); +} + +int64_t godotsharp_packed_vector2_array_size(const PackedVector2Array *p_self) { + return p_self->size(); +} + +int64_t godotsharp_packed_vector3_array_size(const PackedVector3Array *p_self) { + return p_self->size(); +} + +int64_t godotsharp_packed_vector4_array_size(const PackedVector4Array *p_self) { + return p_self->size(); +} + +int64_t godotsharp_packed_color_array_size(const PackedColorArray *p_self) { + return p_self->size(); +} + +int64_t godotsharp_array_size(const Array *p_self) { + return p_self->size(); +} + // The order in this array must match the declaration order of // the methods in 'GodotSharp/Core/NativeInterop/NativeFuncs.cs'. static const void *unmanaged_callbacks[]{ @@ -1796,6 +1844,18 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_var_to_str, (void *)godotsharp_err_print_error, (void *)godotsharp_object_to_string, + (void *)godotsharp_string_size, + (void *)godotsharp_packed_byte_array_size, + (void *)godotsharp_packed_int32_array_size, + (void *)godotsharp_packed_int64_array_size, + (void *)godotsharp_packed_float32_array_size, + (void *)godotsharp_packed_float64_array_size, + (void *)godotsharp_packed_string_array_size, + (void *)godotsharp_packed_vector2_array_size, + (void *)godotsharp_packed_vector3_array_size, + (void *)godotsharp_packed_vector4_array_size, + (void *)godotsharp_packed_color_array_size, + (void *)godotsharp_array_size, }; const void **godotsharp::get_runtime_interop_funcs(int32_t &r_size) {