Merge pull request #105928 from Ivorforce/cowdata-reserve-capacity

Core: Add `reserve` function to `Array`, `Vector`, and `String`
This commit is contained in:
Thaddeus Crews 2025-09-30 11:19:13 -05:00
commit 62933b683e
No known key found for this signature in database
GPG key ID: 8C6E5FEB5FC03CCC
11 changed files with 441 additions and 201 deletions

View file

@ -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 <typename T>
_FORCE_INLINE_ void copy_arr_placement(T *p_dst, const T *p_src, size_t p_num) {
if constexpr (std::is_trivially_copyable_v<T>) {
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 <typename T>
_FORCE_INLINE_ void destruct_arr_placement(T *p_dst, size_t p_num) {
if constexpr (!std::is_trivially_destructible_v<T>) {
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 * Wonders of having own array functions, you can actually check the length of
* an allocated-with memnew_arr() array * an allocated-with memnew_arr() array

View file

@ -1253,7 +1253,7 @@ Vector<float> String::split_floats_mk(const Vector<String> &p_splitters, bool p_
String buffer = *this; String buffer = *this;
while (true) { while (true) {
int idx; int idx = 0;
int end = findmk(p_splitters, from, &idx); int end = findmk(p_splitters, from, &idx);
int spl_len = 1; int spl_len = 1;
if (end < 0) { if (end < 0) {
@ -1308,7 +1308,7 @@ Vector<int> String::split_ints_mk(const Vector<String> &p_splitters, bool p_allo
int len = length(); int len = length();
while (true) { while (true) {
int idx; int idx = 0;
int end = findmk(p_splitters, from, &idx); int end = findmk(p_splitters, from, &idx);
int spl_len = 1; int spl_len = 1;
if (end < 0) { if (end < 0) {

View file

@ -177,10 +177,10 @@ class [[nodiscard]] CharStringT {
public: public:
_FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); } _FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); }
_FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); } _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 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_ bool is_empty() const { return length() == 0; }
_FORCE_INLINE_ operator Span<T>() const { return Span(ptr(), length()); } _FORCE_INLINE_ operator Span<T>() const { return Span(ptr(), length()); }
@ -297,10 +297,10 @@ public:
_FORCE_INLINE_ char32_t *ptrw() { return _cowdata.ptrw(); } _FORCE_INLINE_ char32_t *ptrw() { return _cowdata.ptrw(); }
_FORCE_INLINE_ const char32_t *ptr() const { return _cowdata.ptr(); } _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 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_ bool is_empty() const { return length() == 0; }
_FORCE_INLINE_ operator Span<char32_t>() const { return Span(ptr(), length()); } _FORCE_INLINE_ operator Span<char32_t>() const { return Span(ptr(), length()); }
@ -317,6 +317,11 @@ public:
/// New characters are not initialized, and should be set by the caller. /// New characters are not initialized, and should be set by the caller.
Error resize_uninitialized(int64_t p_size) { return _cowdata.resize<false>(p_size); } Error resize_uninitialized(int64_t p_size) { return _cowdata.resize<false>(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 { _FORCE_INLINE_ const char32_t &operator[](int p_index) const {
if (unlikely(p_index == _cowdata.size())) { if (unlikely(p_index == _cowdata.size())) {
return _null; return _null;

View file

@ -40,9 +40,16 @@
static_assert(std::is_trivially_destructible_v<std::atomic<uint64_t>>); static_assert(std::is_trivially_destructible_v<std::atomic<uint64_t>>);
// Silences false-positive warnings.
GODOT_GCC_WARNING_PUSH GODOT_GCC_WARNING_PUSH
GODOT_GCC_WARNING_IGNORE("-Wplacement-new") // Silence a false positive warning (see GH-52119). 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("-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 <typename T> template <typename T>
class CowData { class CowData {
@ -52,15 +59,16 @@ public:
static constexpr USize MAX_INT = INT64_MAX; static constexpr USize MAX_INT = INT64_MAX;
private: private:
// Alignment: ↓ max_align_t ↓ USize ↓ max_align_t // Alignment: ↓ max_align_t ↓ USize ↓ USize ↓ max_align_t
// ┌────────────────────┬──┬─────────────┬──┬───────────... // ┌────────────────────┬──┬───────────────┬──┬─────────────┬──┬───────────...
// │ SafeNumeric<USize> │░░│ USize │░░│ T[] // │ SafeNumeric<USize> │░░│ USize │░░│ USize │░░│ T[]
// │ ref. count │░░│ data size │░░│ data // │ ref. count │░░│ data capacity │░░│ data size │░░│ data
// └────────────────────┴──┴─────────────┴──┴───────────... // └────────────────────┴──┴───────────────┴──┴─────────────┴──┴───────────...
// Offset: ↑ REF_COUNT_OFFSET ↑ SIZE_OFFSET ↑ DATA_OFFSET // Offset: ↑ REF_COUNT_OFFSET ↑ CAPACITY_OFFSET ↑ SIZE_OFFSET ↑ DATA_OFFSET
static constexpr size_t REF_COUNT_OFFSET = 0; static constexpr size_t REF_COUNT_OFFSET = 0;
static constexpr size_t SIZE_OFFSET = Memory::get_aligned_address(REF_COUNT_OFFSET + sizeof(SafeNumeric<USize>), alignof(USize)); static constexpr size_t CAPACITY_OFFSET = Memory::get_aligned_address(REF_COUNT_OFFSET + sizeof(SafeNumeric<USize>), 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)); static constexpr size_t DATA_OFFSET = Memory::get_aligned_address(SIZE_OFFSET + sizeof(USize), alignof(max_align_t));
mutable T *_ptr = nullptr; mutable T *_ptr = nullptr;
@ -71,20 +79,19 @@ private:
return (T *)(p_ptr + DATA_OFFSET); return (T *)(p_ptr + DATA_OFFSET);
} }
/// Note: Assumes _ptr != nullptr.
_FORCE_INLINE_ SafeNumeric<USize> *_get_refcount() const { _FORCE_INLINE_ SafeNumeric<USize> *_get_refcount() const {
if (!_ptr) {
return nullptr;
}
return (SafeNumeric<USize> *)((uint8_t *)_ptr - DATA_OFFSET + REF_COUNT_OFFSET); return (SafeNumeric<USize> *)((uint8_t *)_ptr - DATA_OFFSET + REF_COUNT_OFFSET);
} }
/// Note: Assumes _ptr != nullptr.
_FORCE_INLINE_ USize *_get_size() const { _FORCE_INLINE_ USize *_get_size() const {
if (!_ptr) { return (USize *)((uint8_t *)_ptr - DATA_OFFSET + SIZE_OFFSET);
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) { _FORCE_INLINE_ static USize _get_alloc_size(USize p_elements) {
@ -121,22 +128,29 @@ private:
void _ref(const CowData *p_from); void _ref(const CowData *p_from);
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 /// Allocates a backing array of the given capacity. The reference count is initialized to 1, size to 0.
// the only reference to it. The buffer is populated with as many element copies from the old /// It is the responsibility of the caller to:
// array as possible. /// - Ensure _ptr == nullptr
// It is the responsibility of the caller to populate newly allocated space up to p_size. /// - Ensure p_capacity > 0
Error _fork_allocate(USize p_size); Error _alloc(USize p_capacity);
Error _copy_on_write() { return _fork_allocate(size()); }
// Allocates a backing array of the given capacity. The reference count is initialized to 1. /// Re-allocates the backing array to the given capacity.
// It is the responsibility of the caller to populate the array and the new size property. /// It is the responsibility of the caller to:
Error _alloc(USize p_alloc_size); /// - 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. /// Create a new buffer and copies over elements from the old buffer.
// It is the responsibility of the caller to populate the array and the new size property. /// Elements are inserted first from the start, then a gap is left uninitialized, and then elements are inserted from the back.
// The caller must also make sure there are no other references to the data, as pointers may /// It is the responsibility of the caller to:
// be invalidated. /// - Construct elements in the gap.
Error _realloc(USize p_alloc_size); /// - 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: public:
void operator=(const CowData<T> &p_from) { _ref(p_from); } void operator=(const CowData<T> &p_from) { _ref(p_from); }
@ -151,7 +165,8 @@ public:
} }
_FORCE_INLINE_ T *ptrw() { _FORCE_INLINE_ T *ptrw() {
_copy_on_write(); // If forking fails, we can only crash.
CRASH_COND(_copy_on_write());
return _ptr; return _ptr;
} }
@ -159,27 +174,25 @@ public:
return _ptr; return _ptr;
} }
_FORCE_INLINE_ Size size() const { _FORCE_INLINE_ Size size() const { return !_ptr ? 0 : *_get_size(); }
USize *size = (USize *)_get_size(); _FORCE_INLINE_ USize capacity() const { return !_ptr ? 0 : *_get_capacity(); }
if (size) { _FORCE_INLINE_ USize refcount() const { return !_ptr ? 0 : *_get_refcount(); }
return *size;
} else {
return 0;
}
}
_FORCE_INLINE_ void clear() { _unref(); } _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) { _FORCE_INLINE_ void set(Size p_index, const T &p_elem) {
ERR_FAIL_INDEX(p_index, size()); 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; _ptr[p_index] = p_elem;
} }
_FORCE_INLINE_ T &get_m(Size p_index) { _FORCE_INLINE_ T &get_m(Size p_index) {
CRASH_BAD_INDEX(p_index, size()); 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]; return _ptr[p_index];
} }
@ -189,33 +202,15 @@ public:
return _ptr[p_index]; return _ptr[p_index];
} }
template <bool p_initialize = true> template <bool p_init = false>
Error resize(Size p_size); Error resize(Size p_size);
_FORCE_INLINE_ void remove_at(Size p_index) { Error reserve(USize p_min_capacity);
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); _FORCE_INLINE_ void remove_at(Size p_index);
}
Error insert(Size p_pos, const T &p_val) { Error insert(Size p_pos, const T &p_val);
Size new_size = size() + 1; Error push_back(const T &p_val);
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;
}
_FORCE_INLINE_ operator Span<T>() const { return Span<T>(ptr(), size()); } _FORCE_INLINE_ operator Span<T>() const { return Span<T>(ptr(), size()); }
_FORCE_INLINE_ Span<T> span() const { return operator Span<T>(); } _FORCE_INLINE_ Span<T> span() const { return operator Span<T>(); }
@ -236,8 +231,7 @@ void CowData<T>::_unref() {
return; return;
} }
SafeNumeric<USize> *refc = _get_refcount(); if (_get_refcount()->decrement() > 0) {
if (refc->decrement() > 0) {
// Data is still in use elsewhere. // Data is still in use elsewhere.
_ptr = nullptr; _ptr = nullptr;
return; return;
@ -251,17 +245,16 @@ void CowData<T>::_unref() {
// observe it through a reference to us. In this case, it may try to access the buffer, // 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 // which is illegal after some of the elements in it have already been destructed, and
// may lead to a segmentation fault. // may lead to a segmentation fault.
USize current_size = *_get_size(); USize current_size = size();
T *prev_ptr = _ptr; T *prev_ptr = _ptr;
_ptr = nullptr; _ptr = nullptr;
if constexpr (!std::is_trivially_destructible_v<T>) { destruct_arr_placement(prev_ptr, current_size);
for (USize i = 0; i < current_size; ++i) {
prev_ptr[i].~T();
}
}
// 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); Memory::free_static((uint8_t *)prev_ptr - DATA_OFFSET, false);
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
@ -272,86 +265,133 @@ void CowData<T>::_unref() {
} }
template <typename T> template <typename T>
Error CowData<T>::_fork_allocate(USize p_size) { void CowData<T>::remove_at(Size p_index) {
if (p_size == 0) { const Size prev_size = size();
// Wants to clean up. ERR_FAIL_INDEX(p_index, prev_size);
if (prev_size == 1) {
// Removing the only element.
_unref(); _unref();
return OK; return;
} }
USize alloc_size; const USize new_size = prev_size - 1;
ERR_FAIL_COND_V(!_get_alloc_size_checked(p_size, &alloc_size), ERR_OUT_OF_MEMORY);
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 <typename T>
Error CowData<T>::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) { if (!_ptr) {
// We had no data before; just allocate a new array. _alloc(1);
const Error error = _alloc(alloc_size); *_get_size() = 1;
if (error) {
return error;
}
} else if (_get_refcount()->get() == 1) { } else if (_get_refcount()->get() == 1) {
// Resize in-place. if ((USize)new_size > capacity()) {
// NOTE: This case is not just an optimization, but required, as some callers depend on // Need to grow.
// `_copy_on_write()` calls not changing the pointer after the first fork const Error error = _realloc(new_size);
// (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<T>) {
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 (error) { if (error) {
// Out of memory; the current array is still valid though.
return error; 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 { } else {
// Resize by forking. // Insert new element by forking.
// Use the max of capacity and new_size, to ensure we don't accidentally shrink after reserve.
// Create a temporary CowData to hold ownership over our _ptr. const USize new_capacity = MAX(capacity(), (USize)new_size);
// It will be used to copy elements from the old buffer over to our new buffer. const Error error = _copy_to_new_buffer(new_capacity, p_pos, 1, size() - p_pos);
// 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);
if (error) { 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; 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<T>) {
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. // Create the new element at the given index.
*_get_size() = p_size; memnew_placement(_ptr + p_pos, T(p_val));
return OK; return OK;
} }
template <typename T>
Error CowData<T>::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 <typename T>
Error CowData<T>::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 <typename T> template <typename T>
template <bool p_initialize> template <bool p_initialize>
Error CowData<T>::resize(Size p_size) { Error CowData<T>::resize(Size p_size) {
@ -359,41 +399,102 @@ Error CowData<T>::resize(Size p_size) {
const Size prev_size = size(); const Size prev_size = size();
if (p_size == prev_size) { if (p_size == prev_size) {
// Caller wants to stay the same size, so we do nothing.
return OK; return OK;
} }
const Error error = _fork_allocate(p_size); if (p_size > prev_size) {
// Caller wants to grow.
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) { if (error) {
return error; return error;
} }
if constexpr (p_initialize) {
if (p_size > prev_size) {
memnew_arr_placement(_ptr + prev_size, p_size - prev_size);
} }
} else { } else {
static_assert(std::is_trivially_destructible_v<T>); // 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);
}
*_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 <typename T> template <typename T>
Error CowData<T>::_alloc(USize p_alloc_size) { Error CowData<T>::_alloc(USize p_min_capacity) {
uint8_t *mem_new = (uint8_t *)Memory::alloc_static(p_alloc_size + DATA_OFFSET, false); 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); ERR_FAIL_NULL_V(mem_new, ERR_OUT_OF_MEMORY);
_ptr = _get_data_ptr(mem_new); _ptr = _get_data_ptr(mem_new);
// If we alloc, we're guaranteed to be the only reference. // If we alloc, we're guaranteed to be the only reference.
new (_get_refcount()) SafeNumeric<USize>(1); new (_get_refcount()) SafeNumeric<USize>(1);
*_get_size() = 0;
// The actual capacity is whatever we can stuff into the alloc_size.
*_get_capacity() = alloc_size / sizeof(T);
return OK; return OK;
} }
template <typename T> template <typename T>
Error CowData<T>::_realloc(USize p_alloc_size) { Error CowData<T>::_realloc(USize p_min_capacity) {
uint8_t *mem_new = (uint8_t *)Memory::realloc_static(((uint8_t *)_ptr) - DATA_OFFSET, p_alloc_size + DATA_OFFSET, false); USize bytes;
ERR_FAIL_COND_V(!_get_alloc_size_checked(p_min_capacity, &bytes), ERR_OUT_OF_MEMORY);
return _realloc_bytes(bytes);
}
template <typename T>
Error CowData<T>::_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); ERR_FAIL_NULL_V(mem_new, ERR_OUT_OF_MEMORY);
_ptr = _get_data_ptr(mem_new); _ptr = _get_data_ptr(mem_new);
@ -401,10 +502,55 @@ Error CowData<T>::_realloc(USize p_alloc_size) {
// If we realloc, we're guaranteed to be the only reference. // If we realloc, we're guaranteed to be the only reference.
// So the reference was 1 and was copied to be 1 again. // So the reference was 1 and was copied to be 1 again.
DEV_ASSERT(_get_refcount()->get() == 1); 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; return OK;
} }
template <typename T>
Error CowData<T>::_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 <typename T>
Error CowData<T>::_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 <typename T> template <typename T>
void CowData<T>::_ref(const CowData *p_from) { void CowData<T>::_ref(const CowData *p_from) {
_ref(*p_from); _ref(*p_from);
@ -429,15 +575,10 @@ void CowData<T>::_ref(const CowData &p_from) {
template <typename T> template <typename T>
CowData<T>::CowData(std::initializer_list<T> p_init) { CowData<T>::CowData(std::initializer_list<T> p_init) {
Error err = resize(p_init.size()); CRASH_COND(_alloc(p_init.size()));
if (err != OK) {
return;
}
Size i = 0; copy_arr_placement(_ptr, p_init.begin(), p_init.size());
for (const T &element : p_init) { *_get_size() = p_init.size();
set(i++, element);
}
} }
GODOT_GCC_WARNING_POP GODOT_GCC_WARNING_POP

View file

@ -323,13 +323,7 @@ public:
ret.resize(count); ret.resize(count);
T *w = ret.ptrw(); T *w = ret.ptrw();
if (w) { if (w) {
if constexpr (std::is_trivially_copyable_v<T>) { copy_arr_placement(w, data, count);
memcpy(w, data, sizeof(T) * count);
} else {
for (U i = 0; i < count; i++) {
w[i] = data[i];
}
}
} }
return ret; return ret;
} }

View file

@ -64,13 +64,14 @@ class Vector {
public: public:
VectorWriteProxy<T> write; VectorWriteProxy<T> write;
typedef typename CowData<T>::Size Size; typedef typename CowData<T>::Size Size;
typedef typename CowData<T>::USize USize;
private: private:
CowData<T> _cowdata; CowData<T> _cowdata;
public: public:
// Must take a copy instead of a reference (see GH-31736). // 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 _FORCE_INLINE_ bool append(const T &p_elem) { return push_back(p_elem); } //alias
void fill(T p_elem); void fill(T p_elem);
@ -89,6 +90,7 @@ public:
_FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); } _FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); }
_FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); } _FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); }
_FORCE_INLINE_ Size size() const { return _cowdata.size(); } _FORCE_INLINE_ Size size() const { return _cowdata.size(); }
_FORCE_INLINE_ USize capacity() const { return _cowdata.capacity(); }
_FORCE_INLINE_ operator Span<T>() const { return _cowdata.span(); } _FORCE_INLINE_ operator Span<T>() const { return _cowdata.span(); }
_FORCE_INLINE_ Span<T> span() const { return _cowdata.span(); } _FORCE_INLINE_ Span<T> span() const { return _cowdata.span(); }
@ -119,6 +121,11 @@ public:
return _cowdata.template resize<false>(p_size); return _cowdata.template resize<false>(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); } _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). // 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); } Error insert(Size p_pos, T p_val) { return _cowdata.insert(p_pos, p_val); }
@ -351,15 +358,6 @@ void Vector<T>::append_array(Vector<T> p_other) {
} }
} }
template <typename T>
bool Vector<T>::push_back(T p_elem) {
Error err = resize(size() + 1);
ERR_FAIL_COND_V(err, true);
set(size() - 1, p_elem);
return false;
}
template <typename T> template <typename T>
void Vector<T>::fill(T p_elem) { void Vector<T>::fill(T p_elem) {
T *p = ptrw(); T *p = ptrw();

View file

@ -313,6 +313,11 @@ Error Array::resize(int p_new_size) {
return err; 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) { 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."); ERR_FAIL_COND_V_MSG(_p->read_only, ERR_LOCKED, "Array is in read-only state.");
Variant value = p_value; Variant value = p_value;

View file

@ -135,6 +135,7 @@ public:
_FORCE_INLINE_ void append(const Variant &p_value) { push_back(p_value); } //for python compatibility _FORCE_INLINE_ void append(const Variant &p_value) { push_back(p_value); } //for python compatibility
void append_array(const Array &p_array); void append_array(const Array &p_array);
Error resize(int p_new_size); Error resize(int p_new_size);
Error reserve(int p_new_size);
Error insert(int p_pos, const Variant &p_value); Error insert(int p_pos, const Variant &p_value);
void remove_at(int p_pos); void remove_at(int p_pos);

View file

@ -538,7 +538,7 @@ namespace Godot.NativeInterop
public readonly unsafe int Size public readonly unsafe int Size
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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# // 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 public readonly unsafe bool IsReadOnly
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -782,12 +776,6 @@ namespace Godot.NativeInterop
{ {
private IntPtr _writeProxy; private IntPtr _writeProxy;
public unsafe godot_variant* _ptr; 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 public readonly unsafe godot_variant* Elements
@ -805,7 +793,7 @@ namespace Godot.NativeInterop
public readonly unsafe int Size public readonly unsafe int Size
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _p != null ? _p->Size : 0; get => (int)NativeFuncs.godotsharp_array_size(in this);
} }
public readonly unsafe bool IsReadOnly public readonly unsafe bool IsReadOnly
@ -936,7 +924,7 @@ namespace Godot.NativeInterop
public readonly unsafe int Size public readonly unsafe int Size
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 public readonly unsafe int Size
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 public readonly unsafe int Size
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 public readonly unsafe int Size
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 public readonly unsafe int Size
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 public readonly unsafe int Size
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 public readonly unsafe int Size
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 public readonly unsafe int Size
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 public readonly unsafe int Size
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 public readonly unsafe int Size
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; get => (int)NativeFuncs.godotsharp_packed_color_array_size(in this);
} }
} }

View file

@ -606,5 +606,31 @@ namespace Godot.NativeInterop
// Object // Object
public static partial void godotsharp_object_to_string(IntPtr ptr, out godot_string r_str); 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);
} }
} }

View file

@ -1568,6 +1568,54 @@ void godotsharp_object_to_string(Object *p_ptr, godot_string *r_str) {
} }
#endif #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 order in this array must match the declaration order of
// the methods in 'GodotSharp/Core/NativeInterop/NativeFuncs.cs'. // the methods in 'GodotSharp/Core/NativeInterop/NativeFuncs.cs'.
static const void *unmanaged_callbacks[]{ static const void *unmanaged_callbacks[]{
@ -1796,6 +1844,18 @@ static const void *unmanaged_callbacks[]{
(void *)godotsharp_var_to_str, (void *)godotsharp_var_to_str,
(void *)godotsharp_err_print_error, (void *)godotsharp_err_print_error,
(void *)godotsharp_object_to_string, (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) { const void **godotsharp::get_runtime_interop_funcs(int32_t &r_size) {