diff --git a/include/godot_cpp/core/method_bind.hpp b/include/godot_cpp/core/method_bind.hpp index 59fb85d44..0ffe4de8a 100644 --- a/include/godot_cpp/core/method_bind.hpp +++ b/include/godot_cpp/core/method_bind.hpp @@ -111,7 +111,7 @@ class MethodBind { } void set_argument_names(const LocalVector &p_names); - LocalVector get_argument_names() const; + const LocalVector &get_argument_names() const; virtual GDExtensionClassMethodArgumentMetadata get_argument_metadata(int p_argument) const = 0; diff --git a/include/godot_cpp/templates/local_vector.hpp b/include/godot_cpp/templates/local_vector.hpp index 9d1d2a747..2af35eec5 100644 --- a/include/godot_cpp/templates/local_vector.hpp +++ b/include/godot_cpp/templates/local_vector.hpp @@ -46,32 +46,48 @@ namespace godot { // Otherwise, it grows exponentially (the default and what you want in most cases). template class LocalVector { + static_assert(!force_trivial, "force_trivial is no longer supported. Use resize_uninitialized instead."); + private: U count = 0; U capacity = 0; T *data = nullptr; + template + void _resize(U p_size) { + if (p_size < count) { + if constexpr (!std::is_trivially_destructible_v) { + for (U i = p_size; i < count; i++) { + data[i].~T(); + } + } + count = p_size; + } else if (p_size > count) { + reserve(p_size); + if constexpr (p_init) { + memnew_arr_placement(data + count, p_size - count); + } else { + static_assert(std::is_trivially_destructible_v, "T must be trivially destructible to resize uninitialized"); + } + count = p_size; + } + } + public: - _FORCE_INLINE_ T *ptr() { return data; } - _FORCE_INLINE_ const T *ptr() const { return data; } + _FORCE_INLINE_ T *ptr() _LIFETIME_BOUND_ { return data; } + _FORCE_INLINE_ const T *ptr() const _LIFETIME_BOUND_ { return data; } _FORCE_INLINE_ U size() const { return count; } - _FORCE_INLINE_ Span span() const { return Span(data, count); } - _FORCE_INLINE_ operator Span() const { return span(); } + _FORCE_INLINE_ Span span() const _LIFETIME_BOUND_ { return Span(data, count); } + _FORCE_INLINE_ operator Span() const _LIFETIME_BOUND_ { return span(); } // Must take a copy instead of a reference (see GH-31736). _FORCE_INLINE_ void push_back(T p_elem) { if (unlikely(count == capacity)) { - capacity = tight ? (capacity + 1) : MAX((U)1, capacity << 1); - data = (T *)memrealloc(data, capacity * sizeof(T)); - CRASH_COND_MSG(!data, "Out of memory"); + reserve(count + 1); } - if constexpr (!std::is_trivially_constructible_v && !force_trivial) { - memnew_placement(&data[count++], T(p_elem)); - } else { - data[count++] = std::move(p_elem); - } + memnew_placement(&data[count++], T(std::move(p_elem))); } void remove_at(U p_index) { @@ -80,9 +96,7 @@ class LocalVector { for (U i = p_index; i < count; i++) { data[i] = std::move(data[i + 1]); } - if constexpr (!std::is_trivially_destructible_v && !force_trivial) { - data[count].~T(); - } + data[count].~T(); } /// Removes the item copying the last value into the position of the one to @@ -93,9 +107,7 @@ class LocalVector { if (count > p_index) { data[p_index] = std::move(data[count]); } - if constexpr (!std::is_trivially_destructible_v && !force_trivial) { - data[count].~T(); - } + data[count].~T(); } _FORCE_INLINE_ bool erase(const T &p_val) { @@ -107,6 +119,15 @@ class LocalVector { return false; } + bool erase_unordered(const T &p_val) { + int64_t idx = find(p_val); + if (idx >= 0) { + remove_at_unordered(idx); + return true; + } + return false; + } + U erase_multiple_unordered(const T &p_val) { U from = 0; U occurrences = 0; @@ -123,11 +144,14 @@ class LocalVector { return occurrences; } - void invert() { + void reverse() { for (U i = 0; i < count / 2; i++) { SWAP(data[i], data[count - i - 1]); } } +#ifndef DISABLE_DEPRECATED + [[deprecated("Use reverse() instead")]] void invert() { reverse(); } +#endif _FORCE_INLINE_ void clear() { resize(0); } _FORCE_INLINE_ void reset() { @@ -140,42 +164,48 @@ class LocalVector { } _FORCE_INLINE_ bool is_empty() const { return count == 0; } _FORCE_INLINE_ U get_capacity() const { return capacity; } - _FORCE_INLINE_ void reserve(U p_size) { - p_size = tight ? p_size : Math::nearest_power_of_2_templated(p_size); + void reserve(U p_size) { if (p_size > capacity) { - capacity = p_size; + if (tight) { + capacity = p_size; + } else { + // Try 1.5x the current capacity. + // This ratio was chosen because it is close to the ideal growth rate of the golden ratio. + // See https://archive.ph/Z2R8w for details. + capacity = MAX((U)2, capacity + ((1 + capacity) >> 1)); + // If 1.5x growth isn't enough, just use the needed size exactly. + if (p_size > capacity) { + capacity = p_size; + } + } data = (T *)memrealloc(data, capacity * sizeof(T)); CRASH_COND_MSG(!data, "Out of memory"); + } else if (p_size < count) { + WARN_VERBOSE("reserve() called with a capacity smaller than the current size. This is likely a mistake."); } } + /// Resize the vector. + /// Elements are initialized (or not) depending on what the default C++ behavior for T is. + /// Note: If force_trivial is set, this will behave like resize_uninitialized instead. void resize(U p_size) { - if (p_size < count) { - if constexpr (!std::is_trivially_destructible_v && !force_trivial) { - for (U i = p_size; i < count; i++) { - data[i].~T(); - } - } - count = p_size; - } else if (p_size > count) { - if (unlikely(p_size > capacity)) { - capacity = tight ? p_size : Math::nearest_power_of_2_templated(p_size); - data = (T *)memrealloc(data, capacity * sizeof(T)); - CRASH_COND_MSG(!data, "Out of memory"); - } - if constexpr (!std::is_trivially_constructible_v && !force_trivial) { - for (U i = count; i < p_size; i++) { - memnew_placement(&data[i], T); - } - } - count = p_size; - } + // Don't init when trivially constructible. + _resize>(p_size); } - _FORCE_INLINE_ const T &operator[](U p_index) const { + + /// Resize and set new values to 0 / false / nullptr. + _FORCE_INLINE_ void resize_initialized(U p_size) { _resize(p_size); } + + /// Resize and keep memory uninitialized. + /// This means that any newly added elements have an unknown value, and are expected to be set after the `resize_uninitialized` call. + /// This is only available for trivially destructible types (otherwise, trivial resize might be UB). + _FORCE_INLINE_ void resize_uninitialized(U p_size) { _resize(p_size); } + + _FORCE_INLINE_ const T &operator[](U p_index) const _LIFETIME_BOUND_ { CRASH_BAD_UNSIGNED_INDEX(p_index, count); return data[p_index]; } - _FORCE_INLINE_ T &operator[](U p_index) { + _FORCE_INLINE_ T &operator[](U p_index) _LIFETIME_BOUND_ { CRASH_BAD_UNSIGNED_INDEX(p_index, count); return data[p_index]; } @@ -230,17 +260,17 @@ class LocalVector { const T *elem_ptr = nullptr; }; - _FORCE_INLINE_ Iterator begin() { + _FORCE_INLINE_ Iterator begin() _LIFETIME_BOUND_ { return Iterator(data); } - _FORCE_INLINE_ Iterator end() { + _FORCE_INLINE_ Iterator end() _LIFETIME_BOUND_ { return Iterator(data + size()); } - _FORCE_INLINE_ ConstIterator begin() const { + _FORCE_INLINE_ ConstIterator begin() const _LIFETIME_BOUND_ { return ConstIterator(ptr()); } - _FORCE_INLINE_ ConstIterator end() const { + _FORCE_INLINE_ ConstIterator end() const _LIFETIME_BOUND_ { return ConstIterator(ptr() + size()); } @@ -257,13 +287,14 @@ class LocalVector { } } - int64_t find(const T &p_val, U p_from = 0) const { - for (U i = p_from; i < count; i++) { - if (data[i] == p_val) { - return int64_t(i); - } + int64_t find(const T &p_val, int64_t p_from = 0) const { + if (p_from < 0) { + p_from = size() + p_from; } - return -1; + if (p_from < 0 || p_from >= size()) { + return -1; + } + return span().find(p_val, p_from); } bool has(const T &p_val) const { @@ -282,7 +313,7 @@ class LocalVector { } void sort() { - sort_custom<_DefaultComparator>(); + sort_custom>(); } void ordered_insert(T p_val) { @@ -295,22 +326,6 @@ class LocalVector { insert(i, p_val); } - operator Vector() const { - Vector ret; - 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]; - } - } - } - return ret; - } - Vector to_byte_array() const { //useful to pass stuff to gpu or variant Vector ret; ret.resize(count * sizeof(T)); @@ -328,7 +343,7 @@ class LocalVector { push_back(element); } } - _FORCE_INLINE_ LocalVector(const LocalVector &p_from) { + _FORCE_INLINE_ explicit LocalVector(const LocalVector &p_from) { resize(p_from.size()); for (U i = 0; i < p_from.count; i++) { data[i] = p_from.data[i]; @@ -384,7 +399,11 @@ class LocalVector { } }; -template -using TightLocalVector = LocalVector; +template +using TightLocalVector = LocalVector; + +// Zero-constructing LocalVector initializes count, capacity and data to 0 and thus empty. +template +struct is_zero_constructible> : std::true_type {}; } // namespace godot diff --git a/src/core/method_bind.cpp b/src/core/method_bind.cpp index 95c702698..65f84974f 100644 --- a/src/core/method_bind.cpp +++ b/src/core/method_bind.cpp @@ -60,7 +60,7 @@ void MethodBind::set_argument_names(const LocalVector &p_names) { argument_names = p_names; } -LocalVector MethodBind::get_argument_names() const { +const LocalVector &MethodBind::get_argument_names() const { return argument_names; }