mirror of
https://github.com/godotengine/godot.git
synced 2025-10-19 16:03:29 +00:00
Merge pull request #105928 from Ivorforce/cowdata-reserve-capacity
Core: Add `reserve` function to `Array`, `Vector`, and `String`
This commit is contained in:
commit
62933b683e
11 changed files with 441 additions and 201 deletions
|
@ -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
|
||||
* an allocated-with memnew_arr() array
|
||||
|
|
|
@ -1253,7 +1253,7 @@ Vector<float> String::split_floats_mk(const Vector<String> &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<int> String::split_ints_mk(const Vector<String> &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) {
|
||||
|
|
|
@ -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<T>() 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<char32_t>() 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<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 {
|
||||
if (unlikely(p_index == _cowdata.size())) {
|
||||
return _null;
|
||||
|
|
|
@ -40,9 +40,16 @@
|
|||
|
||||
static_assert(std::is_trivially_destructible_v<std::atomic<uint64_t>>);
|
||||
|
||||
// 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 <typename T>
|
||||
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> │░░│ 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 │░░│ 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<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));
|
||||
|
||||
mutable T *_ptr = nullptr;
|
||||
|
@ -71,20 +79,19 @@ private:
|
|||
return (T *)(p_ptr + DATA_OFFSET);
|
||||
}
|
||||
|
||||
/// Note: Assumes _ptr != nullptr.
|
||||
_FORCE_INLINE_ SafeNumeric<USize> *_get_refcount() const {
|
||||
if (!_ptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return (SafeNumeric<USize> *)((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);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -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<T> &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 <bool p_initialize = true>
|
||||
template <bool p_init = false>
|
||||
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<T>() const { return Span<T>(ptr(), size()); }
|
||||
_FORCE_INLINE_ Span<T> span() const { return operator Span<T>(); }
|
||||
|
@ -236,8 +231,7 @@ void CowData<T>::_unref() {
|
|||
return;
|
||||
}
|
||||
|
||||
SafeNumeric<USize> *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<T>::_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<T>) {
|
||||
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<T>::_unref() {
|
|||
}
|
||||
|
||||
template <typename T>
|
||||
Error CowData<T>::_fork_allocate(USize p_size) {
|
||||
if (p_size == 0) {
|
||||
// Wants to clean up.
|
||||
void CowData<T>::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 <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) {
|
||||
// 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<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 ((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<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.
|
||||
*_get_size() = p_size;
|
||||
// Create the new element at the given index.
|
||||
memnew_placement(_ptr + p_pos, T(p_val));
|
||||
|
||||
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 <bool p_initialize>
|
||||
Error CowData<T>::resize(Size p_size) {
|
||||
|
@ -359,41 +399,102 @@ Error CowData<T>::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 (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) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if constexpr (p_initialize) {
|
||||
if (p_size > prev_size) {
|
||||
memnew_arr_placement(_ptr + prev_size, p_size - prev_size);
|
||||
}
|
||||
} 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;
|
||||
} 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>
|
||||
Error CowData<T>::_alloc(USize p_alloc_size) {
|
||||
uint8_t *mem_new = (uint8_t *)Memory::alloc_static(p_alloc_size + DATA_OFFSET, false);
|
||||
Error CowData<T>::_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<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;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Error CowData<T>::_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<T>::_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 <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);
|
||||
|
||||
_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.
|
||||
// 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 <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>
|
||||
void CowData<T>::_ref(const CowData *p_from) {
|
||||
_ref(*p_from);
|
||||
|
@ -429,15 +575,10 @@ void CowData<T>::_ref(const CowData &p_from) {
|
|||
|
||||
template <typename T>
|
||||
CowData<T>::CowData(std::initializer_list<T> 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
|
||||
|
|
|
@ -323,13 +323,7 @@ public:
|
|||
ret.resize(count);
|
||||
T *w = ret.ptrw();
|
||||
if (w) {
|
||||
if constexpr (std::is_trivially_copyable_v<T>) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -64,13 +64,14 @@ class Vector {
|
|||
public:
|
||||
VectorWriteProxy<T> write;
|
||||
typedef typename CowData<T>::Size Size;
|
||||
typedef typename CowData<T>::USize USize;
|
||||
|
||||
private:
|
||||
CowData<T> _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<T>() 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);
|
||||
}
|
||||
|
||||
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<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>
|
||||
void Vector<T>::fill(T p_elem) {
|
||||
T *p = ptrw();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue