diff options
Diffstat (limited to 'source/blender/blenlib')
-rw-r--r-- | source/blender/blenlib/BLI_generic_array.hh | 255 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_generic_pointer.hh | 123 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_generic_span.hh | 169 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_generic_value_map.hh | 111 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_generic_vector_array.hh | 147 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_generic_virtual_array.hh | 877 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_generic_virtual_vector_array.hh | 173 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_virtual_array.hh | 10 | ||||
-rw-r--r-- | source/blender/blenlib/CMakeLists.txt | 13 | ||||
-rw-r--r-- | source/blender/blenlib/intern/generic_vector_array.cc | 96 | ||||
-rw-r--r-- | source/blender/blenlib/intern/generic_virtual_array.cc | 724 | ||||
-rw-r--r-- | source/blender/blenlib/intern/generic_virtual_vector_array.cc | 53 | ||||
-rw-r--r-- | source/blender/blenlib/tests/BLI_generic_array_test.cc | 117 | ||||
-rw-r--r-- | source/blender/blenlib/tests/BLI_generic_span_test.cc | 53 | ||||
-rw-r--r-- | source/blender/blenlib/tests/BLI_generic_vector_array_test.cc | 43 |
15 files changed, 2958 insertions, 6 deletions
diff --git a/source/blender/blenlib/BLI_generic_array.hh b/source/blender/blenlib/BLI_generic_array.hh new file mode 100644 index 00000000000..e1b6b29874a --- /dev/null +++ b/source/blender/blenlib/BLI_generic_array.hh @@ -0,0 +1,255 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bli + * + * This is a generic counterpart to #blender::Array, used when the type is not known at runtime. + * + * `GArray` should generally only be used for passing data around in dynamic contexts. + * It does not support a few things that #blender::Array supports: + * - Small object optimization / inline buffer. + * - Exception safety and various more specific constructors. + */ + +#include "BLI_allocator.hh" +#include "BLI_cpp_type.hh" +#include "BLI_generic_span.hh" + +namespace blender { + +template< + /** + * The allocator used by this array. Should rarely be changed, except when you don't want that + * MEM_* functions are used internally. + */ + typename Allocator = GuardedAllocator> +class GArray { + protected: + /** The type of the data in the array, will be null after the array is default constructed, + * but a value should be assigned before any other interaction with the array. */ + const CPPType *type_ = nullptr; + void *data_ = nullptr; + int64_t size_ = 0; + + Allocator allocator_; + + public: + /** + * The default constructor creates an empty array, the only situation in which the type is + * allowed to be null. This default constructor exists so `GArray` can be used in containers, + * but the type should be supplied before doing anything else to the array. + */ + GArray(Allocator allocator = {}) noexcept : allocator_(allocator) + { + } + + GArray(NoExceptConstructor, Allocator allocator = {}) noexcept : GArray(allocator) + { + } + + /** + * Create and allocate a new array, with elements default constructed + * (which does not do anything for trivial types). + */ + GArray(const CPPType &type, int64_t size, Allocator allocator = {}) : GArray(type, allocator) + { + BLI_assert(size >= 0); + size_ = size; + data_ = this->allocate(size_); + type_->default_construct_n(data_, size_); + } + + /** + * Create an empty array with just a type. + */ + GArray(const CPPType &type, Allocator allocator = {}) : GArray(allocator) + { + type_ = &type; + } + + /** + * Take ownership of a buffer with a provided size. The buffer should be + * allocated with the same allocator provided to the constructor. + */ + GArray(const CPPType &type, void *buffer, int64_t size, Allocator allocator = {}) + : GArray(type, allocator) + { + BLI_assert(size >= 0); + BLI_assert(buffer != nullptr || size == 0); + BLI_assert(type_->pointer_has_valid_alignment(buffer)); + + data_ = buffer; + size_ = size; + } + + /** + * Create an array by copying values from a generic span. + */ + GArray(const GSpan span, Allocator allocator = {}) : GArray(span.type(), span.size(), allocator) + { + if (span.data() != nullptr) { + BLI_assert(span.size() != 0); + /* Use copy assign rather than construct since the memory is already initialized. */ + type_->copy_assign_n(span.data(), data_, size_); + } + } + + /** + * Create an array by copying values from another generic array. + */ + GArray(const GArray &other) : GArray(other.as_span(), other.allocator()) + { + } + + /** + * Create an array by taking ownership of another array's data, clearing the data in the other. + */ + GArray(GArray &&other) : GArray(other.type(), other.data(), other.size(), other.allocator()) + { + other.data_ = nullptr; + other.size_ = 0; + } + + ~GArray() + { + if (data_ != nullptr) { + type_->destruct_n(data_, size_); + this->deallocate(data_); + } + } + + GArray &operator=(const GArray &other) + { + return copy_assign_container(*this, other); + } + + GArray &operator=(GArray &&other) + { + return move_assign_container(*this, std::move(other)); + } + + const CPPType &type() const + { + BLI_assert(type_ != nullptr); + return *type_; + } + + bool is_empty() const + { + return size_ == 0; + } + + /** + * Return the number of elements in the array (not the size in bytes). + */ + int64_t size() const + { + return size_; + } + + /** + * Get a pointer to the beginning of the array. + */ + const void *data() const + { + return data_; + } + void *data() + { + return data_; + } + + const void *operator[](int64_t index) const + { + BLI_assert(index < size_); + return POINTER_OFFSET(data_, type_->size() * index); + } + + void *operator[](int64_t index) + { + BLI_assert(index < size_); + return POINTER_OFFSET(data_, type_->size() * index); + } + + operator GSpan() const + { + BLI_assert(type_ != nullptr); + return GSpan(*type_, data_, size_); + } + + operator GMutableSpan() + { + BLI_assert(type_ != nullptr); + return GMutableSpan(*type_, data_, size_); + } + + GSpan as_span() const + { + return *this; + } + + GMutableSpan as_mutable_span() + { + return *this; + } + + /** + * Access the allocator used by this array. + */ + Allocator &allocator() + { + return allocator_; + } + const Allocator &allocator() const + { + return allocator_; + } + + /** + * Destruct values and create a new array of the given size. The values in the new array are + * default constructed. + */ + void reinitialize(const int64_t new_size) + { + BLI_assert(new_size >= 0); + int64_t old_size = size_; + + type_->destruct_n(data_, size_); + size_ = 0; + + if (new_size <= old_size) { + type_->default_construct_n(data_, new_size); + } + else { + void *new_data = this->allocate(new_size); + try { + type_->default_construct_n(new_data, new_size); + } + catch (...) { + this->deallocate(new_data); + throw; + } + this->deallocate(data_); + data_ = new_data; + } + + size_ = new_size; + } + + private: + void *allocate(int64_t size) + { + const int64_t item_size = type_->size(); + const int64_t alignment = type_->alignment(); + return allocator_.allocate(static_cast<size_t>(size) * item_size, alignment, AT); + } + + void deallocate(void *ptr) + { + allocator_.deallocate(ptr); + } +}; + +} // namespace blender diff --git a/source/blender/blenlib/BLI_generic_pointer.hh b/source/blender/blenlib/BLI_generic_pointer.hh new file mode 100644 index 00000000000..226f76c3d33 --- /dev/null +++ b/source/blender/blenlib/BLI_generic_pointer.hh @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_cpp_type.hh" + +namespace blender { + +/** + * A generic non-const pointer whose type is only known at runtime. + */ +class GMutablePointer { + private: + const CPPType *type_ = nullptr; + void *data_ = nullptr; + + public: + GMutablePointer() = default; + + GMutablePointer(const CPPType *type, void *data = nullptr) : type_(type), data_(data) + { + /* If there is data, there has to be a type. */ + BLI_assert(data_ == nullptr || type_ != nullptr); + } + + GMutablePointer(const CPPType &type, void *data = nullptr) : GMutablePointer(&type, data) + { + } + + template<typename T> GMutablePointer(T *data) : GMutablePointer(&CPPType::get<T>(), data) + { + } + + void *get() const + { + return data_; + } + + const CPPType *type() const + { + return type_; + } + + template<typename T> T *get() const + { + BLI_assert(this->is_type<T>()); + return static_cast<T *>(data_); + } + + template<typename T> bool is_type() const + { + return type_ != nullptr && type_->is<T>(); + } + + template<typename T> T relocate_out() + { + BLI_assert(this->is_type<T>()); + T value; + type_->relocate_assign(data_, &value); + data_ = nullptr; + type_ = nullptr; + return value; + } + + void destruct() + { + BLI_assert(data_ != nullptr); + type_->destruct(data_); + } +}; + +/** + * A generic const pointer whose type is only known at runtime. + */ +class GPointer { + private: + const CPPType *type_ = nullptr; + const void *data_ = nullptr; + + public: + GPointer() = default; + + GPointer(GMutablePointer ptr) : type_(ptr.type()), data_(ptr.get()) + { + } + + GPointer(const CPPType *type, const void *data = nullptr) : type_(type), data_(data) + { + /* If there is data, there has to be a type. */ + BLI_assert(data_ == nullptr || type_ != nullptr); + } + + GPointer(const CPPType &type, const void *data = nullptr) : type_(&type), data_(data) + { + } + + template<typename T> GPointer(T *data) : GPointer(&CPPType::get<T>(), data) + { + } + + const void *get() const + { + return data_; + } + + const CPPType *type() const + { + return type_; + } + + template<typename T> const T *get() const + { + BLI_assert(this->is_type<T>()); + return static_cast<const T *>(data_); + } + + template<typename T> bool is_type() const + { + return type_ != nullptr && type_->is<T>(); + } +}; + +} // namespace blender diff --git a/source/blender/blenlib/BLI_generic_span.hh b/source/blender/blenlib/BLI_generic_span.hh new file mode 100644 index 00000000000..f4f93735e06 --- /dev/null +++ b/source/blender/blenlib/BLI_generic_span.hh @@ -0,0 +1,169 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bli + */ + +#include "BLI_cpp_type.hh" +#include "BLI_span.hh" + +namespace blender { + +/** + * A generic span. It behaves just like a blender::Span<T>, but the type is only known at run-time. + */ +class GSpan { + protected: + const CPPType *type_; + const void *data_; + int64_t size_; + + public: + GSpan(const CPPType &type, const void *buffer, int64_t size) + : type_(&type), data_(buffer), size_(size) + { + BLI_assert(size >= 0); + BLI_assert(buffer != nullptr || size == 0); + BLI_assert(type.pointer_has_valid_alignment(buffer)); + } + + GSpan(const CPPType &type) : GSpan(type, nullptr, 0) + { + } + + template<typename T> + GSpan(Span<T> array) + : GSpan(CPPType::get<T>(), static_cast<const void *>(array.data()), array.size()) + { + } + + const CPPType &type() const + { + return *type_; + } + + bool is_empty() const + { + return size_ == 0; + } + + int64_t size() const + { + return size_; + } + + const void *data() const + { + return data_; + } + + const void *operator[](int64_t index) const + { + BLI_assert(index < size_); + return POINTER_OFFSET(data_, type_->size() * index); + } + + template<typename T> Span<T> typed() const + { + BLI_assert(type_->is<T>()); + return Span<T>(static_cast<const T *>(data_), size_); + } + + GSpan slice(const int64_t start, int64_t size) const + { + BLI_assert(start >= 0); + BLI_assert(size >= 0); + const int64_t new_size = std::max<int64_t>(0, std::min(size, size_ - start)); + return GSpan(*type_, POINTER_OFFSET(data_, type_->size() * start), new_size); + } + + GSpan slice(const IndexRange range) const + { + return this->slice(range.start(), range.size()); + } +}; + +/** + * A generic mutable span. It behaves just like a blender::MutableSpan<T>, but the type is only + * known at run-time. + */ +class GMutableSpan { + protected: + const CPPType *type_; + void *data_; + int64_t size_; + + public: + GMutableSpan(const CPPType &type, void *buffer, int64_t size) + : type_(&type), data_(buffer), size_(size) + { + BLI_assert(size >= 0); + BLI_assert(buffer != nullptr || size == 0); + BLI_assert(type.pointer_has_valid_alignment(buffer)); + } + + GMutableSpan(const CPPType &type) : GMutableSpan(type, nullptr, 0) + { + } + + template<typename T> + GMutableSpan(MutableSpan<T> array) + : GMutableSpan(CPPType::get<T>(), static_cast<void *>(array.begin()), array.size()) + { + } + + operator GSpan() const + { + return GSpan(*type_, data_, size_); + } + + const CPPType &type() const + { + return *type_; + } + + bool is_empty() const + { + return size_ == 0; + } + + int64_t size() const + { + return size_; + } + + void *data() const + { + return data_; + } + + void *operator[](int64_t index) const + { + BLI_assert(index >= 0); + BLI_assert(index < size_); + return POINTER_OFFSET(data_, type_->size() * index); + } + + template<typename T> MutableSpan<T> typed() const + { + BLI_assert(type_->is<T>()); + return MutableSpan<T>(static_cast<T *>(data_), size_); + } + + GMutableSpan slice(const int64_t start, int64_t size) const + { + BLI_assert(start >= 0); + BLI_assert(size >= 0); + const int64_t new_size = std::max<int64_t>(0, std::min(size, size_ - start)); + return GMutableSpan(*type_, POINTER_OFFSET(data_, type_->size() * start), new_size); + } + + GMutableSpan slice(IndexRange range) const + { + return this->slice(range.start(), range.size()); + } +}; + +} // namespace blender diff --git a/source/blender/blenlib/BLI_generic_value_map.hh b/source/blender/blenlib/BLI_generic_value_map.hh new file mode 100644 index 00000000000..bd8408526b8 --- /dev/null +++ b/source/blender/blenlib/BLI_generic_value_map.hh @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_generic_pointer.hh" +#include "BLI_linear_allocator.hh" +#include "BLI_map.hh" + +namespace blender { + +/** + * This is a map that stores key-value-pairs. What makes it special is that the type of values does + * not have to be known at compile time. There just has to be a corresponding CPPType. + */ +template<typename Key> class GValueMap { + private: + /* Used to allocate values owned by this container. */ + LinearAllocator<> &allocator_; + Map<Key, GMutablePointer> values_; + + public: + GValueMap(LinearAllocator<> &allocator) : allocator_(allocator) + { + } + + ~GValueMap() + { + /* Destruct all values that are still in the map. */ + for (GMutablePointer value : values_.values()) { + value.destruct(); + } + } + + /* Add a value to the container. The container becomes responsible for destructing the value that + * is passed in. The caller remains responsible for freeing the value after it has been + * destructed. */ + template<typename ForwardKey> void add_new_direct(ForwardKey &&key, GMutablePointer value) + { + values_.add_new_as(std::forward<ForwardKey>(key), value); + } + + /* Add a value to the container that is move constructed from the given value. The caller remains + * responsible for destructing and freeing the given value. */ + template<typename ForwardKey> void add_new_by_move(ForwardKey &&key, GMutablePointer value) + { + const CPPType &type = *value.type(); + void *buffer = allocator_.allocate(type.size(), type.alignment()); + type.move_construct(value.get(), buffer); + values_.add_new_as(std::forward<ForwardKey>(key), GMutablePointer{type, buffer}); + } + + /* Add a value to the container that is copy constructed from the given value. The caller remains + * responsible for destructing and freeing the given value. */ + template<typename ForwardKey> void add_new_by_copy(ForwardKey &&key, GPointer value) + { + const CPPType &type = *value.type(); + void *buffer = allocator_.allocate(type.size(), type.alignment()); + type.copy_construct(value.get(), buffer); + values_.add_new_as(std::forward<ForwardKey>(key), GMutablePointer{type, buffer}); + } + + /* Add a value to the container. */ + template<typename ForwardKey, typename T> void add_new(ForwardKey &&key, T &&value) + { + if constexpr (std::is_rvalue_reference_v<T>) { + this->add_new_by_move(std::forward<ForwardKey>(key), &value); + } + else { + this->add_new_by_copy(std::forward<ForwardKey>(key), &value); + } + } + + /* Remove the value for the given name from the container and remove it. The caller is + * responsible for freeing it. The lifetime of the referenced memory might be bound to lifetime + * of the container. */ + template<typename ForwardKey> GMutablePointer extract(const ForwardKey &key) + { + return values_.pop_as(key); + } + + template<typename ForwardKey> GPointer lookup(const ForwardKey &key) const + { + return values_.lookup_as(key); + } + + /* Remove the value for the given name from the container and remove it. */ + template<typename T, typename ForwardKey> T extract(const ForwardKey &key) + { + GMutablePointer value = values_.pop_as(key); + const CPPType &type = *value.type(); + BLI_assert(type.is<T>()); + T return_value; + type.relocate_assign(value.get(), &return_value); + return return_value; + } + + template<typename T, typename ForwardKey> const T &lookup(const ForwardKey &key) const + { + GMutablePointer value = values_.lookup_as(key); + BLI_assert(value.is_type<T>()); + BLI_assert(value.get() != nullptr); + return *(const T *)value.get(); + } + + template<typename ForwardKey> bool contains(const ForwardKey &key) const + { + return values_.contains_as(key); + } +}; + +} // namespace blender diff --git a/source/blender/blenlib/BLI_generic_vector_array.hh b/source/blender/blenlib/BLI_generic_vector_array.hh new file mode 100644 index 00000000000..c98817df4e3 --- /dev/null +++ b/source/blender/blenlib/BLI_generic_vector_array.hh @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bli + * + * A`GVectorArray` is a container for a fixed amount of dynamically growing vectors with a generic + * data type. Its main use case is to store many small vectors with few separate allocations. Using + * this structure is generally more efficient than allocating each vector separately. + */ + +#include "BLI_array.hh" +#include "BLI_generic_virtual_vector_array.hh" +#include "BLI_linear_allocator.hh" + +namespace blender { + +/* An array of vectors containing elements of a generic type. */ +class GVectorArray : NonCopyable, NonMovable { + private: + struct Item { + void *start = nullptr; + int64_t length = 0; + int64_t capacity = 0; + }; + + /* Use a linear allocator to pack many small vectors together. Currently, memory from reallocated + * vectors is not reused. This can be improved in the future. */ + LinearAllocator<> allocator_; + /* The data type of individual elements. */ + const CPPType &type_; + /* The size of an individual element. This is inlined from `type_.size()` for easier access. */ + const int64_t element_size_; + /* The individual vectors. */ + Array<Item> items_; + + public: + GVectorArray() = delete; + + GVectorArray(const CPPType &type, int64_t array_size); + + ~GVectorArray(); + + int64_t size() const + { + return items_.size(); + } + + bool is_empty() const + { + return items_.is_empty(); + } + + const CPPType &type() const + { + return type_; + } + + void append(int64_t index, const void *value); + + /* Add multiple elements to a single vector. */ + void extend(int64_t index, const GVArray &values); + void extend(int64_t index, GSpan values); + + /* Add multiple elements to multiple vectors. */ + void extend(IndexMask mask, const GVVectorArray &values); + void extend(IndexMask mask, const GVectorArray &values); + + void clear(IndexMask mask); + + GMutableSpan operator[](int64_t index); + GSpan operator[](int64_t index) const; + + private: + void realloc_to_at_least(Item &item, int64_t min_capacity); +}; + +/* A non-owning typed mutable reference to an `GVectorArray`. It simplifies access when the type of + * the data is known at compile time. */ +template<typename T> class GVectorArray_TypedMutableRef { + private: + GVectorArray *vector_array_; + + public: + GVectorArray_TypedMutableRef(GVectorArray &vector_array) : vector_array_(&vector_array) + { + BLI_assert(vector_array_->type().is<T>()); + } + + int64_t size() const + { + return vector_array_->size(); + } + + bool is_empty() const + { + return vector_array_->is_empty(); + } + + void append(const int64_t index, const T &value) + { + vector_array_->append(index, &value); + } + + void extend(const int64_t index, const Span<T> values) + { + vector_array_->extend(index, values); + } + + void extend(const int64_t index, const VArray<T> &values) + { + vector_array_->extend(index, values); + } + + MutableSpan<T> operator[](const int64_t index) + { + return (*vector_array_)[index].typed<T>(); + } +}; + +/* A generic virtual vector array implementation for a `GVectorArray`. */ +class GVVectorArray_For_GVectorArray : public GVVectorArray { + private: + const GVectorArray &vector_array_; + + public: + GVVectorArray_For_GVectorArray(const GVectorArray &vector_array) + : GVVectorArray(vector_array.type(), vector_array.size()), vector_array_(vector_array) + { + } + + protected: + int64_t get_vector_size_impl(const int64_t index) const override + { + return vector_array_[index].size(); + } + + void get_vector_element_impl(const int64_t index, + const int64_t index_in_vector, + void *r_value) const override + { + type_->copy_assign(vector_array_[index][index_in_vector], r_value); + } +}; + +} // namespace blender diff --git a/source/blender/blenlib/BLI_generic_virtual_array.hh b/source/blender/blenlib/BLI_generic_virtual_array.hh new file mode 100644 index 00000000000..f4c9e745cf9 --- /dev/null +++ b/source/blender/blenlib/BLI_generic_virtual_array.hh @@ -0,0 +1,877 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bli + * + * A generic virtual array is the same as a virtual array, except for the fact that the data type + * is only known at runtime. + */ + +#include "BLI_generic_array.hh" +#include "BLI_generic_span.hh" +#include "BLI_timeit.hh" +#include "BLI_virtual_array.hh" + +namespace blender { + +/* -------------------------------------------------------------------- */ +/** \name #GVArrayImpl and #GVMutableArrayImpl. + * \{ */ + +class GVArray; +class GVArrayImpl; +class GVMutableArray; +class GVMutableArrayImpl; + +/* A generically typed version of #VArrayImpl. */ +class GVArrayImpl { + protected: + const CPPType *type_; + int64_t size_; + + public: + GVArrayImpl(const CPPType &type, int64_t size); + virtual ~GVArrayImpl() = default; + + const CPPType &type() const; + + int64_t size() const; + + virtual void get(int64_t index, void *r_value) const; + virtual void get_to_uninitialized(int64_t index, void *r_value) const = 0; + + virtual bool is_span() const; + virtual GSpan get_internal_span() const; + + virtual bool is_single() const; + virtual void get_internal_single(void *UNUSED(r_value)) const; + + virtual void materialize(const IndexMask mask, void *dst) const; + virtual void materialize_to_uninitialized(const IndexMask mask, void *dst) const; + + virtual bool try_assign_VArray(void *varray) const; + virtual bool may_have_ownership() const; +}; + +/* A generic version of #VMutableArrayImpl. */ +class GVMutableArrayImpl : public GVArrayImpl { + public: + GVMutableArrayImpl(const CPPType &type, int64_t size); + + virtual void set_by_copy(int64_t index, const void *value); + virtual void set_by_relocate(int64_t index, void *value); + virtual void set_by_move(int64_t index, void *value) = 0; + + virtual void set_all(const void *src); + + virtual bool try_assign_VMutableArray(void *varray) const; +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #GVArray and #GVMutableArray + * \{ */ + +namespace detail { +struct GVArrayAnyExtraInfo { + const GVArrayImpl *(*get_varray)(const void *buffer) = + [](const void *UNUSED(buffer)) -> const GVArrayImpl * { return nullptr; }; + + template<typename StorageT> static GVArrayAnyExtraInfo get(); +}; +} // namespace detail + +class GVMutableArray; + +/** + * Utility class to reduce code duplication between #GVArray and #GVMutableArray. + * It pretty much follows #VArrayCommon. Don't use this class outside of this header. + */ +class GVArrayCommon { + protected: + /** + * See #VArrayCommon for more information. The inline buffer is a bit larger here, because + * generic virtual array implementations often require a bit more space than typed ones. + */ + using Storage = Any<detail::GVArrayAnyExtraInfo, 40, 8>; + + const GVArrayImpl *impl_ = nullptr; + Storage storage_; + + protected: + GVArrayCommon(); + GVArrayCommon(const GVArrayCommon &other); + GVArrayCommon(GVArrayCommon &&other) noexcept; + GVArrayCommon(const GVArrayImpl *impl); + GVArrayCommon(std::shared_ptr<const GVArrayImpl> impl); + ~GVArrayCommon(); + + template<typename ImplT, typename... Args> void emplace(Args &&...args); + + void copy_from(const GVArrayCommon &other); + void move_from(GVArrayCommon &&other) noexcept; + + const GVArrayImpl *impl_from_storage() const; + + public: + const CPPType &type() const; + operator bool() const; + + int64_t size() const; + bool is_empty() const; + IndexRange index_range() const; + + template<typename T> bool try_assign_VArray(VArray<T> &varray) const; + bool may_have_ownership() const; + + void materialize(void *dst) const; + void materialize(const IndexMask mask, void *dst) const; + + void materialize_to_uninitialized(void *dst) const; + void materialize_to_uninitialized(const IndexMask mask, void *dst) const; + + /** + * Returns true when the virtual array is stored as a span internally. + */ + bool is_span() const; + /** + * Returns the internally used span of the virtual array. This invokes undefined behavior if the + * virtual array is not stored as a span internally. + */ + GSpan get_internal_span() const; + + /** + * Returns true when the virtual array returns the same value for every index. + */ + bool is_single() const; + /** + * Copies the value that is used for every element into `r_value`, which is expected to point to + * initialized memory. This invokes undefined behavior if the virtual array would not return the + * same value for every index. + */ + void get_internal_single(void *r_value) const; + /** + * Same as `get_internal_single`, but `r_value` points to initialized memory. + */ + void get_internal_single_to_uninitialized(void *r_value) const; + + void get(int64_t index, void *r_value) const; + /** + * Returns a copy of the value at the given index. Usually a typed virtual array should + * be used instead, but sometimes this is simpler when only a few indices are needed. + */ + template<typename T> T get(int64_t index) const; + void get_to_uninitialized(int64_t index, void *r_value) const; +}; + +/** Generic version of #VArray. */ +class GVArray : public GVArrayCommon { + private: + friend GVMutableArray; + + public: + GVArray() = default; + + GVArray(const GVArray &other); + GVArray(GVArray &&other) noexcept; + GVArray(const GVArrayImpl *impl); + GVArray(std::shared_ptr<const GVArrayImpl> impl); + + template<typename T> GVArray(const VArray<T> &varray); + template<typename T> VArray<T> typed() const; + + template<typename ImplT, typename... Args> static GVArray For(Args &&...args); + + static GVArray ForSingle(const CPPType &type, int64_t size, const void *value); + static GVArray ForSingleRef(const CPPType &type, int64_t size, const void *value); + static GVArray ForSingleDefault(const CPPType &type, int64_t size); + static GVArray ForSpan(GSpan span); + static GVArray ForGArray(GArray<> array); + static GVArray ForEmpty(const CPPType &type); + + GVArray slice(IndexRange slice) const; + + GVArray &operator=(const GVArray &other); + GVArray &operator=(GVArray &&other) noexcept; + + const GVArrayImpl *get_implementation() const + { + return impl_; + } +}; + +/** Generic version of #VMutableArray. */ +class GVMutableArray : public GVArrayCommon { + public: + GVMutableArray() = default; + GVMutableArray(const GVMutableArray &other); + GVMutableArray(GVMutableArray &&other) noexcept; + GVMutableArray(GVMutableArrayImpl *impl); + GVMutableArray(std::shared_ptr<GVMutableArrayImpl> impl); + + template<typename T> GVMutableArray(const VMutableArray<T> &varray); + template<typename T> VMutableArray<T> typed() const; + + template<typename ImplT, typename... Args> static GVMutableArray For(Args &&...args); + + static GVMutableArray ForSpan(GMutableSpan span); + + operator GVArray() const &; + operator GVArray() &&noexcept; + + GVMutableArray &operator=(const GVMutableArray &other); + GVMutableArray &operator=(GVMutableArray &&other) noexcept; + + GMutableSpan get_internal_span() const; + + template<typename T> bool try_assign_VMutableArray(VMutableArray<T> &varray) const; + + void set_by_copy(int64_t index, const void *value); + void set_by_move(int64_t index, void *value); + void set_by_relocate(int64_t index, void *value); + + void fill(const void *value); + /** + * Copy the values from the source buffer to all elements in the virtual array. + */ + void set_all(const void *src); + + GVMutableArrayImpl *get_implementation() const; + + private: + GVMutableArrayImpl *get_impl() const; +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #GVArray_GSpan and #GVMutableArray_GSpan. + * \{ */ + +/* A generic version of VArray_Span. */ +class GVArray_GSpan : public GSpan { + private: + GVArray varray_; + void *owned_data_ = nullptr; + + public: + GVArray_GSpan(GVArray varray); + ~GVArray_GSpan(); +}; + +/* A generic version of VMutableArray_Span. */ +class GVMutableArray_GSpan : public GMutableSpan { + private: + GVMutableArray varray_; + void *owned_data_ = nullptr; + bool save_has_been_called_ = false; + bool show_not_saved_warning_ = true; + + public: + GVMutableArray_GSpan(GVMutableArray varray, bool copy_values_to_span = true); + ~GVMutableArray_GSpan(); + + void save(); + void disable_not_applied_warning(); +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Conversions between generic and typed virtual arrays. + * \{ */ + +/* Used to convert a typed virtual array into a generic one. */ +template<typename T> class GVArrayImpl_For_VArray : public GVArrayImpl { + protected: + VArray<T> varray_; + + public: + GVArrayImpl_For_VArray(VArray<T> varray) + : GVArrayImpl(CPPType::get<T>(), varray.size()), varray_(std::move(varray)) + { + } + + protected: + void get(const int64_t index, void *r_value) const override + { + *(T *)r_value = varray_[index]; + } + + void get_to_uninitialized(const int64_t index, void *r_value) const override + { + new (r_value) T(varray_[index]); + } + + bool is_span() const override + { + return varray_.is_span(); + } + + GSpan get_internal_span() const override + { + return GSpan(varray_.get_internal_span()); + } + + bool is_single() const override + { + return varray_.is_single(); + } + + void get_internal_single(void *r_value) const override + { + *(T *)r_value = varray_.get_internal_single(); + } + + void materialize(const IndexMask mask, void *dst) const override + { + varray_.materialize(mask, MutableSpan((T *)dst, mask.min_array_size())); + } + + void materialize_to_uninitialized(const IndexMask mask, void *dst) const override + { + varray_.materialize_to_uninitialized(mask, MutableSpan((T *)dst, mask.min_array_size())); + } + + bool try_assign_VArray(void *varray) const override + { + *(VArray<T> *)varray = varray_; + return true; + } + + bool may_have_ownership() const override + { + return varray_.may_have_ownership(); + } +}; + +/* Used to convert any generic virtual array into a typed one. */ +template<typename T> class VArrayImpl_For_GVArray : public VArrayImpl<T> { + protected: + GVArray varray_; + + public: + VArrayImpl_For_GVArray(GVArray varray) : VArrayImpl<T>(varray.size()), varray_(std::move(varray)) + { + BLI_assert(varray_); + BLI_assert(varray_.type().template is<T>()); + } + + protected: + T get(const int64_t index) const override + { + T value; + varray_.get(index, &value); + return value; + } + + bool is_span() const override + { + return varray_.is_span(); + } + + Span<T> get_internal_span() const override + { + return varray_.get_internal_span().template typed<T>(); + } + + bool is_single() const override + { + return varray_.is_single(); + } + + T get_internal_single() const override + { + T value; + varray_.get_internal_single(&value); + return value; + } + + bool try_assign_GVArray(GVArray &varray) const override + { + varray = varray_; + return true; + } + + bool may_have_ownership() const override + { + return varray_.may_have_ownership(); + } +}; + +/* Used to convert any typed virtual mutable array into a generic one. */ +template<typename T> class GVMutableArrayImpl_For_VMutableArray : public GVMutableArrayImpl { + protected: + VMutableArray<T> varray_; + + public: + GVMutableArrayImpl_For_VMutableArray(VMutableArray<T> varray) + : GVMutableArrayImpl(CPPType::get<T>(), varray.size()), varray_(std::move(varray)) + { + } + + protected: + void get(const int64_t index, void *r_value) const override + { + *(T *)r_value = varray_[index]; + } + + void get_to_uninitialized(const int64_t index, void *r_value) const override + { + new (r_value) T(varray_[index]); + } + + bool is_span() const override + { + return varray_.is_span(); + } + + GSpan get_internal_span() const override + { + Span<T> span = varray_.get_internal_span(); + return span; + } + + bool is_single() const override + { + return varray_.is_single(); + } + + void get_internal_single(void *r_value) const override + { + *(T *)r_value = varray_.get_internal_single(); + } + + void set_by_copy(const int64_t index, const void *value) override + { + const T &value_ = *(const T *)value; + varray_.set(index, value_); + } + + void set_by_relocate(const int64_t index, void *value) override + { + T &value_ = *(T *)value; + varray_.set(index, std::move(value_)); + value_.~T(); + } + + void set_by_move(const int64_t index, void *value) override + { + T &value_ = *(T *)value; + varray_.set(index, std::move(value_)); + } + + void set_all(const void *src) override + { + varray_.set_all(Span((T *)src, size_)); + } + + void materialize(const IndexMask mask, void *dst) const override + { + varray_.materialize(mask, MutableSpan((T *)dst, mask.min_array_size())); + } + + void materialize_to_uninitialized(const IndexMask mask, void *dst) const override + { + varray_.materialize_to_uninitialized(mask, MutableSpan((T *)dst, mask.min_array_size())); + } + + bool try_assign_VArray(void *varray) const override + { + *(VArray<T> *)varray = varray_; + return true; + } + + bool try_assign_VMutableArray(void *varray) const override + { + *(VMutableArray<T> *)varray = varray_; + return true; + } + + bool may_have_ownership() const override + { + return varray_.may_have_ownership(); + } +}; + +/* Used to convert an generic mutable virtual array into a typed one. */ +template<typename T> class VMutableArrayImpl_For_GVMutableArray : public VMutableArrayImpl<T> { + protected: + GVMutableArray varray_; + + public: + VMutableArrayImpl_For_GVMutableArray(GVMutableArray varray) + : VMutableArrayImpl<T>(varray.size()), varray_(varray) + { + BLI_assert(varray_); + BLI_assert(varray_.type().template is<T>()); + } + + private: + T get(const int64_t index) const override + { + T value; + varray_.get(index, &value); + return value; + } + + void set(const int64_t index, T value) override + { + varray_.set_by_relocate(index, &value); + } + + bool is_span() const override + { + return varray_.is_span(); + } + + Span<T> get_internal_span() const override + { + return varray_.get_internal_span().template typed<T>(); + } + + bool is_single() const override + { + return varray_.is_single(); + } + + T get_internal_single() const override + { + T value; + varray_.get_internal_single(&value); + return value; + } + + bool try_assign_GVArray(GVArray &varray) const override + { + varray = varray_; + return true; + } + + bool try_assign_GVMutableArray(GVMutableArray &varray) const override + { + varray = varray_; + return true; + } + + bool may_have_ownership() const override + { + return varray_.may_have_ownership(); + } +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #GVArrayImpl_For_GSpan. + * \{ */ + +class GVArrayImpl_For_GSpan : public GVMutableArrayImpl { + protected: + void *data_ = nullptr; + const int64_t element_size_; + + public: + GVArrayImpl_For_GSpan(const GMutableSpan span); + + protected: + GVArrayImpl_For_GSpan(const CPPType &type, int64_t size); + + public: + void get(int64_t index, void *r_value) const override; + void get_to_uninitialized(int64_t index, void *r_value) const override; + + void set_by_copy(int64_t index, const void *value) override; + void set_by_move(int64_t index, void *value) override; + void set_by_relocate(int64_t index, void *value) override; + + bool is_span() const override; + GSpan get_internal_span() const override; +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Inline methods for #GVArrayImpl. + * \{ */ + +inline GVArrayImpl::GVArrayImpl(const CPPType &type, const int64_t size) + : type_(&type), size_(size) +{ + BLI_assert(size_ >= 0); +} + +inline const CPPType &GVArrayImpl::type() const +{ + return *type_; +} + +inline int64_t GVArrayImpl::size() const +{ + return size_; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Inline methods for #GVMutableArrayImpl. + * \{ */ + +inline void GVMutableArray::set_by_copy(const int64_t index, const void *value) +{ + BLI_assert(index >= 0); + BLI_assert(index < this->size()); + this->get_impl()->set_by_copy(index, value); +} + +inline void GVMutableArray::set_by_move(const int64_t index, void *value) +{ + BLI_assert(index >= 0); + BLI_assert(index < this->size()); + this->get_impl()->set_by_move(index, value); +} + +inline void GVMutableArray::set_by_relocate(const int64_t index, void *value) +{ + BLI_assert(index >= 0); + BLI_assert(index < this->size()); + this->get_impl()->set_by_relocate(index, value); +} + +template<typename T> +inline bool GVMutableArray::try_assign_VMutableArray(VMutableArray<T> &varray) const +{ + BLI_assert(impl_->type().is<T>()); + return this->get_impl()->try_assign_VMutableArray(&varray); +} + +inline GVMutableArrayImpl *GVMutableArray::get_impl() const +{ + return (GVMutableArrayImpl *)impl_; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Inline methods for #GVArrayCommon. + * \{ */ + +template<typename ImplT, typename... Args> inline void GVArrayCommon::emplace(Args &&...args) +{ + static_assert(std::is_base_of_v<GVArrayImpl, ImplT>); + if constexpr (std::is_copy_constructible_v<ImplT> && Storage::template is_inline_v<ImplT>) { + impl_ = &storage_.template emplace<ImplT>(std::forward<Args>(args)...); + } + else { + std::shared_ptr<const GVArrayImpl> ptr = std::make_shared<ImplT>(std::forward<Args>(args)...); + impl_ = &*ptr; + storage_ = std::move(ptr); + } +} + +/* Copies the value at the given index into the provided storage. The `r_value` pointer is + * expected to point to initialized memory. */ +inline void GVArrayCommon::get(const int64_t index, void *r_value) const +{ + BLI_assert(index >= 0); + BLI_assert(index < this->size()); + impl_->get(index, r_value); +} + +template<typename T> inline T GVArrayCommon::get(const int64_t index) const +{ + BLI_assert(index >= 0); + BLI_assert(index < this->size()); + BLI_assert(this->type().is<T>()); + T value{}; + impl_->get(index, &value); + return value; +} + +/* Same as `get`, but `r_value` is expected to point to uninitialized memory. */ +inline void GVArrayCommon::get_to_uninitialized(const int64_t index, void *r_value) const +{ + BLI_assert(index >= 0); + BLI_assert(index < this->size()); + impl_->get_to_uninitialized(index, r_value); +} + +template<typename T> inline bool GVArrayCommon::try_assign_VArray(VArray<T> &varray) const +{ + BLI_assert(impl_->type().is<T>()); + return impl_->try_assign_VArray(&varray); +} + +inline const CPPType &GVArrayCommon::type() const +{ + return impl_->type(); +} + +inline GVArrayCommon::operator bool() const +{ + return impl_ != nullptr; +} + +inline int64_t GVArrayCommon::size() const +{ + if (impl_ == nullptr) { + return 0; + } + return impl_->size(); +} + +inline bool GVArrayCommon::is_empty() const +{ + return this->size() == 0; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Inline methods for #GVArray. + * \{ */ + +namespace detail { +template<typename StorageT> inline GVArrayAnyExtraInfo GVArrayAnyExtraInfo::get() +{ + static_assert(std::is_base_of_v<GVArrayImpl, StorageT> || + is_same_any_v<StorageT, const GVArrayImpl *, std::shared_ptr<const GVArrayImpl>>); + + if constexpr (std::is_base_of_v<GVArrayImpl, StorageT>) { + return {[](const void *buffer) { + return static_cast<const GVArrayImpl *>((const StorageT *)buffer); + }}; + } + else if constexpr (std::is_same_v<StorageT, const GVArrayImpl *>) { + return {[](const void *buffer) { return *(const StorageT *)buffer; }}; + } + else if constexpr (std::is_same_v<StorageT, std::shared_ptr<const GVArrayImpl>>) { + return {[](const void *buffer) { return ((const StorageT *)buffer)->get(); }}; + } + else { + BLI_assert_unreachable(); + return {}; + } +} +} // namespace detail + +template<typename ImplT, typename... Args> inline GVArray GVArray::For(Args &&...args) +{ + static_assert(std::is_base_of_v<GVArrayImpl, ImplT>); + GVArray varray; + varray.template emplace<ImplT>(std::forward<Args>(args)...); + return varray; +} + +template<typename T> inline GVArray::GVArray(const VArray<T> &varray) +{ + if (!varray) { + return; + } + if (varray.try_assign_GVArray(*this)) { + return; + } + /* Need to check this before the span/single special cases, because otherwise we might loose + * ownership to the referenced data when #varray goes out of scope. */ + if (varray.may_have_ownership()) { + *this = GVArray::For<GVArrayImpl_For_VArray<T>>(varray); + } + else if (varray.is_span()) { + Span<T> data = varray.get_internal_span(); + *this = GVArray::ForSpan(data); + } + else if (varray.is_single()) { + T value = varray.get_internal_single(); + *this = GVArray::ForSingle(CPPType::get<T>(), varray.size(), &value); + } + else { + *this = GVArray::For<GVArrayImpl_For_VArray<T>>(varray); + } +} + +template<typename T> inline VArray<T> GVArray::typed() const +{ + if (!*this) { + return {}; + } + BLI_assert(impl_->type().is<T>()); + VArray<T> varray; + if (this->try_assign_VArray(varray)) { + return varray; + } + if (this->may_have_ownership()) { + return VArray<T>::template For<VArrayImpl_For_GVArray<T>>(*this); + } + if (this->is_span()) { + const Span<T> span = this->get_internal_span().typed<T>(); + return VArray<T>::ForSpan(span); + } + if (this->is_single()) { + T value; + this->get_internal_single(&value); + return VArray<T>::ForSingle(value, this->size()); + } + return VArray<T>::template For<VArrayImpl_For_GVArray<T>>(*this); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Inline methods for #GVMutableArray. + * \{ */ + +template<typename ImplT, typename... Args> +inline GVMutableArray GVMutableArray::For(Args &&...args) +{ + static_assert(std::is_base_of_v<GVMutableArrayImpl, ImplT>); + GVMutableArray varray; + varray.emplace<ImplT>(std::forward<Args>(args)...); + return varray; +} + +template<typename T> inline GVMutableArray::GVMutableArray(const VMutableArray<T> &varray) +{ + if (!varray) { + return; + } + if (varray.try_assign_GVMutableArray(*this)) { + return; + } + if (varray.may_have_ownership()) { + *this = GVMutableArray::For<GVMutableArrayImpl_For_VMutableArray<T>>(varray); + } + else if (varray.is_span()) { + MutableSpan<T> data = varray.get_internal_span(); + *this = GVMutableArray::ForSpan(data); + } + else { + *this = GVMutableArray::For<GVMutableArrayImpl_For_VMutableArray<T>>(varray); + } +} + +template<typename T> inline VMutableArray<T> GVMutableArray::typed() const +{ + if (!*this) { + return {}; + } + BLI_assert(this->type().is<T>()); + VMutableArray<T> varray; + if (this->try_assign_VMutableArray(varray)) { + return varray; + } + if (this->may_have_ownership()) { + return VMutableArray<T>::template For<VMutableArrayImpl_For_GVMutableArray<T>>(*this); + } + if (this->is_span()) { + const MutableSpan<T> span = this->get_internal_span().typed<T>(); + return VMutableArray<T>::ForSpan(span); + } + return VMutableArray<T>::template For<VMutableArrayImpl_For_GVMutableArray<T>>(*this); +} + +/** \} */ + +} // namespace blender diff --git a/source/blender/blenlib/BLI_generic_virtual_vector_array.hh b/source/blender/blenlib/BLI_generic_virtual_vector_array.hh new file mode 100644 index 00000000000..364b1ab33c7 --- /dev/null +++ b/source/blender/blenlib/BLI_generic_virtual_vector_array.hh @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bli + * + * A generic virtual vector array is essentially the same as a virtual vector array, but its data + * type is only known at runtime. + */ + +#include "BLI_generic_virtual_array.hh" +#include "BLI_virtual_vector_array.hh" + +namespace blender { + +/* A generically typed version of `VVectorArray`. */ +class GVVectorArray { + protected: + const CPPType *type_; + int64_t size_; + + public: + GVVectorArray(const CPPType &type, const int64_t size) : type_(&type), size_(size) + { + } + + virtual ~GVVectorArray() = default; + + /* Returns the number of vectors in the vector array. */ + int64_t size() const + { + return size_; + } + + /* Returns true when there is no vector in the vector array. */ + bool is_empty() const + { + return size_ == 0; + } + + const CPPType &type() const + { + return *type_; + } + + /* Returns the size of the vector at the given index. */ + int64_t get_vector_size(const int64_t index) const + { + BLI_assert(index >= 0); + BLI_assert(index < size_); + return this->get_vector_size_impl(index); + } + + /* Copies an element from one of the vectors into `r_value`, which is expected to point to + * initialized memory. */ + void get_vector_element(const int64_t index, const int64_t index_in_vector, void *r_value) const + { + BLI_assert(index >= 0); + BLI_assert(index < size_); + BLI_assert(index_in_vector >= 0); + BLI_assert(index_in_vector < this->get_vector_size(index)); + this->get_vector_element_impl(index, index_in_vector, r_value); + } + + /* Returns true when the same vector is used at every index. */ + bool is_single_vector() const + { + if (size_ == 1) { + return true; + } + return this->is_single_vector_impl(); + } + + protected: + virtual int64_t get_vector_size_impl(int64_t index) const = 0; + + virtual void get_vector_element_impl(int64_t index, + int64_t index_in_vector, + void *r_value) const = 0; + + virtual bool is_single_vector_impl() const + { + return false; + } +}; + +class GVArray_For_GVVectorArrayIndex : public GVArrayImpl { + private: + const GVVectorArray &vector_array_; + const int64_t index_; + + public: + GVArray_For_GVVectorArrayIndex(const GVVectorArray &vector_array, const int64_t index) + : GVArrayImpl(vector_array.type(), vector_array.get_vector_size(index)), + vector_array_(vector_array), + index_(index) + { + } + + protected: + void get(int64_t index_in_vector, void *r_value) const override; + void get_to_uninitialized(int64_t index_in_vector, void *r_value) const override; +}; + +class GVVectorArray_For_SingleGVArray : public GVVectorArray { + private: + GVArray varray_; + + public: + GVVectorArray_For_SingleGVArray(GVArray varray, const int64_t size) + : GVVectorArray(varray.type(), size), varray_(std::move(varray)) + { + } + + protected: + int64_t get_vector_size_impl(int64_t index) const override; + void get_vector_element_impl(int64_t index, + int64_t index_in_vector, + void *r_value) const override; + + bool is_single_vector_impl() const override; +}; + +class GVVectorArray_For_SingleGSpan : public GVVectorArray { + private: + const GSpan span_; + + public: + GVVectorArray_For_SingleGSpan(const GSpan span, const int64_t size) + : GVVectorArray(span.type(), size), span_(span) + { + } + + protected: + int64_t get_vector_size_impl(int64_t UNUSED(index)) const override; + void get_vector_element_impl(int64_t UNUSED(index), + int64_t index_in_vector, + void *r_value) const override; + + bool is_single_vector_impl() const override; +}; + +template<typename T> class VVectorArray_For_GVVectorArray : public VVectorArray<T> { + private: + const GVVectorArray &vector_array_; + + public: + VVectorArray_For_GVVectorArray(const GVVectorArray &vector_array) + : VVectorArray<T>(vector_array.size()), vector_array_(vector_array) + { + } + + protected: + int64_t get_vector_size_impl(const int64_t index) const override + { + return vector_array_.get_vector_size(index); + } + + T get_vector_element_impl(const int64_t index, const int64_t index_in_vector) const override + { + T value; + vector_array_.get_vector_element(index, index_in_vector, &value); + return value; + } + + bool is_single_vector_impl() const override + { + return vector_array_.is_single_vector(); + } +}; + +} // namespace blender diff --git a/source/blender/blenlib/BLI_virtual_array.hh b/source/blender/blenlib/BLI_virtual_array.hh index 16fd706c99d..3aa25bf6819 100644 --- a/source/blender/blenlib/BLI_virtual_array.hh +++ b/source/blender/blenlib/BLI_virtual_array.hh @@ -31,10 +31,8 @@ namespace blender { /** Forward declarations for generic virtual arrays. */ -namespace fn { class GVArray; class GVMutableArray; -}; // namespace fn /** * Implements the specifics of how the elements of a virtual array are accessed. It contains a @@ -154,7 +152,7 @@ template<typename T> class VArrayImpl { * arrays in all cases. * Return true when the virtual array was assigned and false when nothing was done. */ - virtual bool try_assign_GVArray(fn::GVArray &UNUSED(varray)) const + virtual bool try_assign_GVArray(GVArray &UNUSED(varray)) const { return false; } @@ -211,7 +209,7 @@ template<typename T> class VMutableArrayImpl : public VArrayImpl<T> { /** * Similar to #VArrayImpl::try_assign_GVArray but for mutable virtual arrays. */ - virtual bool try_assign_GVMutableArray(fn::GVMutableArray &UNUSED(varray)) const + virtual bool try_assign_GVMutableArray(GVMutableArray &UNUSED(varray)) const { return false; } @@ -743,7 +741,7 @@ template<typename T> class VArrayCommon { } /** See #GVArrayImpl::try_assign_GVArray. */ - bool try_assign_GVArray(fn::GVArray &varray) const + bool try_assign_GVArray(GVArray &varray) const { return impl_->try_assign_GVArray(varray); } @@ -960,7 +958,7 @@ template<typename T> class VMutableArray : public VArrayCommon<T> { } /** See #GVMutableArrayImpl::try_assign_GVMutableArray. */ - bool try_assign_GVMutableArray(fn::GVMutableArray &varray) const + bool try_assign_GVMutableArray(GVMutableArray &varray) const { return this->get_impl()->try_assign_GVMutableArray(varray); } diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 057d94a6f39..6c216f873de 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -65,6 +65,9 @@ set(SRC intern/filereader_memory.c intern/filereader_zstd.c intern/fnmatch.c + intern/generic_vector_array.cc + intern/generic_virtual_array.cc + intern/generic_virtual_vector_array.cc intern/gsqueue.c intern/hash_md5.c intern/hash_mm2a.c @@ -195,6 +198,13 @@ set(SRC BLI_float4x4.hh BLI_fnmatch.h BLI_function_ref.hh + BLI_generic_array.hh + BLI_generic_pointer.hh + BLI_generic_span.hh + BLI_generic_value_map.hh + BLI_generic_vector_array.hh + BLI_generic_virtual_array.hh + BLI_generic_virtual_vector_array.hh BLI_ghash.h BLI_gsqueue.h BLI_hash.h @@ -413,6 +423,9 @@ if(WITH_GTESTS) tests/BLI_expr_pylike_eval_test.cc tests/BLI_fileops_test.cc tests/BLI_function_ref_test.cc + tests/BLI_generic_array_test.cc + tests/BLI_generic_span_test.cc + tests/BLI_generic_vector_array_test.cc tests/BLI_ghash_test.cc tests/BLI_hash_mm2a_test.cc tests/BLI_heap_simple_test.cc diff --git a/source/blender/blenlib/intern/generic_vector_array.cc b/source/blender/blenlib/intern/generic_vector_array.cc new file mode 100644 index 00000000000..b32236bfada --- /dev/null +++ b/source/blender/blenlib/intern/generic_vector_array.cc @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_generic_vector_array.hh" + +namespace blender { + +GVectorArray::GVectorArray(const CPPType &type, const int64_t array_size) + : type_(type), element_size_(type.size()), items_(array_size) +{ +} + +GVectorArray::~GVectorArray() +{ + if (type_.is_trivially_destructible()) { + return; + } + for (Item &item : items_) { + type_.destruct_n(item.start, item.length); + } +} + +void GVectorArray::append(const int64_t index, const void *value) +{ + Item &item = items_[index]; + if (item.length == item.capacity) { + this->realloc_to_at_least(item, item.capacity + 1); + } + + void *dst = POINTER_OFFSET(item.start, element_size_ * item.length); + type_.copy_construct(value, dst); + item.length++; +} + +void GVectorArray::extend(const int64_t index, const GVArray &values) +{ + BLI_assert(values.type() == type_); + for (const int i : IndexRange(values.size())) { + BUFFER_FOR_CPP_TYPE_VALUE(type_, buffer); + values.get(i, buffer); + this->append(index, buffer); + type_.destruct(buffer); + } +} + +void GVectorArray::extend(const int64_t index, const GSpan values) +{ + this->extend(index, GVArray::ForSpan(values)); +} + +void GVectorArray::extend(IndexMask mask, const GVVectorArray &values) +{ + for (const int i : mask) { + GVArray_For_GVVectorArrayIndex array{values, i}; + this->extend(i, GVArray(&array)); + } +} + +void GVectorArray::extend(IndexMask mask, const GVectorArray &values) +{ + GVVectorArray_For_GVectorArray virtual_values{values}; + this->extend(mask, virtual_values); +} + +void GVectorArray::clear(IndexMask mask) +{ + for (const int64_t i : mask) { + Item &item = items_[i]; + type_.destruct_n(item.start, item.length); + item.length = 0; + } +} + +GMutableSpan GVectorArray::operator[](const int64_t index) +{ + Item &item = items_[index]; + return GMutableSpan{type_, item.start, item.length}; +} + +GSpan GVectorArray::operator[](const int64_t index) const +{ + const Item &item = items_[index]; + return GSpan{type_, item.start, item.length}; +} + +void GVectorArray::realloc_to_at_least(Item &item, int64_t min_capacity) +{ + const int64_t new_capacity = std::max(min_capacity, item.length * 2); + + void *new_buffer = allocator_.allocate(element_size_ * new_capacity, type_.alignment()); + type_.relocate_assign_n(item.start, new_buffer, item.length); + + item.start = new_buffer; + item.capacity = new_capacity; +} + +} // namespace blender diff --git a/source/blender/blenlib/intern/generic_virtual_array.cc b/source/blender/blenlib/intern/generic_virtual_array.cc new file mode 100644 index 00000000000..c6abf3624e1 --- /dev/null +++ b/source/blender/blenlib/intern/generic_virtual_array.cc @@ -0,0 +1,724 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_generic_virtual_array.hh" + +namespace blender { + +/* -------------------------------------------------------------------- */ +/** \name #GVArrayImpl + * \{ */ + +void GVArrayImpl::materialize(const IndexMask mask, void *dst) const +{ + for (const int64_t i : mask) { + void *elem_dst = POINTER_OFFSET(dst, type_->size() * i); + this->get(i, elem_dst); + } +} + +void GVArrayImpl::materialize_to_uninitialized(const IndexMask mask, void *dst) const +{ + for (const int64_t i : mask) { + void *elem_dst = POINTER_OFFSET(dst, type_->size() * i); + this->get_to_uninitialized(i, elem_dst); + } +} + +void GVArrayImpl::get(const int64_t index, void *r_value) const +{ + type_->destruct(r_value); + this->get_to_uninitialized(index, r_value); +} + +bool GVArrayImpl::is_span() const +{ + return false; +} + +GSpan GVArrayImpl::get_internal_span() const +{ + BLI_assert(false); + return GSpan(*type_); +} + +bool GVArrayImpl::is_single() const +{ + return false; +} + +void GVArrayImpl::get_internal_single(void *UNUSED(r_value)) const +{ + BLI_assert(false); +} + +bool GVArrayImpl::try_assign_VArray(void *UNUSED(varray)) const +{ + return false; +} + +bool GVArrayImpl::may_have_ownership() const +{ + /* Use true as default to avoid accidentally creating subclasses that have this set to false but + * actually own data. Subclasses should set the to false instead. */ + return true; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #GVMutableArrayImpl + * \{ */ + +GVMutableArrayImpl::GVMutableArrayImpl(const CPPType &type, const int64_t size) + : GVArrayImpl(type, size) +{ +} + +void GVMutableArrayImpl::set_by_copy(const int64_t index, const void *value) +{ + BUFFER_FOR_CPP_TYPE_VALUE(*type_, buffer); + type_->copy_construct(value, buffer); + this->set_by_move(index, buffer); + type_->destruct(buffer); +} + +void GVMutableArrayImpl::set_by_relocate(const int64_t index, void *value) +{ + this->set_by_move(index, value); + type_->destruct(value); +} + +void GVMutableArrayImpl::set_all(const void *src) +{ + if (this->is_span()) { + const GSpan span = this->get_internal_span(); + type_->copy_assign_n(src, const_cast<void *>(span.data()), size_); + } + else { + for (int64_t i : IndexRange(size_)) { + this->set_by_copy(i, POINTER_OFFSET(src, type_->size() * i)); + } + } +} + +void GVMutableArray::fill(const void *value) +{ + if (this->is_span()) { + const GSpan span = this->get_internal_span(); + this->type().fill_assign_n(value, const_cast<void *>(span.data()), this->size()); + } + else { + for (int64_t i : IndexRange(this->size())) { + this->set_by_copy(i, value); + } + } +} + +bool GVMutableArrayImpl::try_assign_VMutableArray(void *UNUSED(varray)) const +{ + return false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #GVArrayImpl_For_GSpan + * \{ */ + +GVArrayImpl_For_GSpan::GVArrayImpl_For_GSpan(const GMutableSpan span) + : GVMutableArrayImpl(span.type(), span.size()), + data_(span.data()), + element_size_(span.type().size()) +{ +} + +GVArrayImpl_For_GSpan::GVArrayImpl_For_GSpan(const CPPType &type, const int64_t size) + : GVMutableArrayImpl(type, size), element_size_(type.size()) +{ +} + +void GVArrayImpl_For_GSpan::get(const int64_t index, void *r_value) const +{ + type_->copy_assign(POINTER_OFFSET(data_, element_size_ * index), r_value); +} + +void GVArrayImpl_For_GSpan::get_to_uninitialized(const int64_t index, void *r_value) const +{ + type_->copy_construct(POINTER_OFFSET(data_, element_size_ * index), r_value); +} + +void GVArrayImpl_For_GSpan::set_by_copy(const int64_t index, const void *value) +{ + type_->copy_assign(value, POINTER_OFFSET(data_, element_size_ * index)); +} + +void GVArrayImpl_For_GSpan::set_by_move(const int64_t index, void *value) +{ + type_->move_construct(value, POINTER_OFFSET(data_, element_size_ * index)); +} + +void GVArrayImpl_For_GSpan::set_by_relocate(const int64_t index, void *value) +{ + type_->relocate_assign(value, POINTER_OFFSET(data_, element_size_ * index)); +} + +bool GVArrayImpl_For_GSpan::is_span() const +{ + return true; +} + +GSpan GVArrayImpl_For_GSpan::get_internal_span() const +{ + return GSpan(*type_, data_, size_); +} + +class GVArrayImpl_For_GSpan_final final : public GVArrayImpl_For_GSpan { + public: + using GVArrayImpl_For_GSpan::GVArrayImpl_For_GSpan; + + private: + bool may_have_ownership() const override + { + return false; + } +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #GVArrayImpl_For_SingleValueRef + * \{ */ + +/* Generic virtual array where each element has the same value. The value is not owned. */ +class GVArrayImpl_For_SingleValueRef : public GVArrayImpl { + protected: + const void *value_ = nullptr; + + public: + GVArrayImpl_For_SingleValueRef(const CPPType &type, const int64_t size, const void *value) + : GVArrayImpl(type, size), value_(value) + { + } + + protected: + GVArrayImpl_For_SingleValueRef(const CPPType &type, const int64_t size) : GVArrayImpl(type, size) + { + } + + void get(const int64_t UNUSED(index), void *r_value) const override + { + type_->copy_assign(value_, r_value); + } + void get_to_uninitialized(const int64_t UNUSED(index), void *r_value) const override + { + type_->copy_construct(value_, r_value); + } + + bool is_span() const override + { + return size_ == 1; + } + GSpan get_internal_span() const override + { + return GSpan{*type_, value_, 1}; + } + + bool is_single() const override + { + return true; + } + void get_internal_single(void *r_value) const override + { + type_->copy_assign(value_, r_value); + } +}; + +class GVArrayImpl_For_SingleValueRef_final final : public GVArrayImpl_For_SingleValueRef { + public: + using GVArrayImpl_For_SingleValueRef::GVArrayImpl_For_SingleValueRef; + + private: + bool may_have_ownership() const override + { + return false; + } +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #GVArrayImpl_For_SingleValue + * \{ */ + +/* Same as GVArrayImpl_For_SingleValueRef, but the value is owned. */ +class GVArrayImpl_For_SingleValue : public GVArrayImpl_For_SingleValueRef, + NonCopyable, + NonMovable { + public: + GVArrayImpl_For_SingleValue(const CPPType &type, const int64_t size, const void *value) + : GVArrayImpl_For_SingleValueRef(type, size) + { + value_ = MEM_mallocN_aligned(type.size(), type.alignment(), __func__); + type.copy_construct(value, (void *)value_); + } + + ~GVArrayImpl_For_SingleValue() override + { + type_->destruct((void *)value_); + MEM_freeN((void *)value_); + } +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #GVArrayImpl_For_SmallTrivialSingleValue + * \{ */ + +/** + * Contains an inline buffer that can store a single value of a trivial type. + * This avoids the allocation that would be done by #GVArrayImpl_For_SingleValue. + */ +template<int BufferSize> class GVArrayImpl_For_SmallTrivialSingleValue : public GVArrayImpl { + private: + AlignedBuffer<BufferSize, 8> buffer_; + + public: + GVArrayImpl_For_SmallTrivialSingleValue(const CPPType &type, + const int64_t size, + const void *value) + : GVArrayImpl(type, size) + { + BLI_assert(type.is_trivial()); + BLI_assert(type.alignment() <= 8); + BLI_assert(type.size() <= BufferSize); + type.copy_construct(value, &buffer_); + } + + private: + void get(const int64_t UNUSED(index), void *r_value) const override + { + this->copy_value_to(r_value); + } + void get_to_uninitialized(const int64_t UNUSED(index), void *r_value) const override + { + this->copy_value_to(r_value); + } + + bool is_single() const override + { + return true; + } + void get_internal_single(void *r_value) const override + { + this->copy_value_to(r_value); + } + + void copy_value_to(void *dst) const + { + memcpy(dst, &buffer_, type_->size()); + } +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #GVArray_GSpan + * \{ */ + +GVArray_GSpan::GVArray_GSpan(GVArray varray) : GSpan(varray.type()), varray_(std::move(varray)) +{ + size_ = varray_.size(); + if (varray_.is_span()) { + data_ = varray_.get_internal_span().data(); + } + else { + owned_data_ = MEM_mallocN_aligned(type_->size() * size_, type_->alignment(), __func__); + varray_.materialize_to_uninitialized(IndexRange(size_), owned_data_); + data_ = owned_data_; + } +} + +GVArray_GSpan::~GVArray_GSpan() +{ + if (owned_data_ != nullptr) { + type_->destruct_n(owned_data_, size_); + MEM_freeN(owned_data_); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #GVMutableArray_GSpan + * \{ */ + +GVMutableArray_GSpan::GVMutableArray_GSpan(GVMutableArray varray, const bool copy_values_to_span) + : GMutableSpan(varray.type()), varray_(std::move(varray)) +{ + size_ = varray_.size(); + if (varray_.is_span()) { + data_ = varray_.get_internal_span().data(); + } + else { + owned_data_ = MEM_mallocN_aligned(type_->size() * size_, type_->alignment(), __func__); + if (copy_values_to_span) { + varray_.materialize_to_uninitialized(IndexRange(size_), owned_data_); + } + else { + type_->default_construct_n(owned_data_, size_); + } + data_ = owned_data_; + } +} + +GVMutableArray_GSpan::~GVMutableArray_GSpan() +{ + if (show_not_saved_warning_) { + if (!save_has_been_called_) { + std::cout << "Warning: Call `apply()` to make sure that changes persist in all cases.\n"; + } + } + if (owned_data_ != nullptr) { + type_->destruct_n(owned_data_, size_); + MEM_freeN(owned_data_); + } +} + +void GVMutableArray_GSpan::save() +{ + save_has_been_called_ = true; + if (data_ != owned_data_) { + return; + } + const int64_t element_size = type_->size(); + for (int64_t i : IndexRange(size_)) { + varray_.set_by_copy(i, POINTER_OFFSET(owned_data_, element_size * i)); + } +} + +void GVMutableArray_GSpan::disable_not_applied_warning() +{ + show_not_saved_warning_ = false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #GVArrayImpl_For_SlicedGVArray + * \{ */ + +class GVArrayImpl_For_SlicedGVArray : public GVArrayImpl { + protected: + GVArray varray_; + int64_t offset_; + IndexRange slice_; + + public: + GVArrayImpl_For_SlicedGVArray(GVArray varray, const IndexRange slice) + : GVArrayImpl(varray.type(), slice.size()), + varray_(std::move(varray)), + offset_(slice.start()), + slice_(slice) + { + BLI_assert(slice.one_after_last() <= varray_.size()); + } + + void get(const int64_t index, void *r_value) const override + { + varray_.get(index + offset_, r_value); + } + + void get_to_uninitialized(const int64_t index, void *r_value) const override + { + varray_.get_to_uninitialized(index + offset_, r_value); + } + + bool is_span() const override + { + return varray_.is_span(); + } + GSpan get_internal_span() const override + { + return varray_.get_internal_span().slice(slice_); + } + + bool is_single() const override + { + return varray_.is_single(); + } + void get_internal_single(void *r_value) const override + { + varray_.get_internal_single(r_value); + } +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #GVArrayCommon + * \{ */ + +GVArrayCommon::GVArrayCommon() = default; + +GVArrayCommon::GVArrayCommon(const GVArrayCommon &other) : storage_(other.storage_) +{ + impl_ = this->impl_from_storage(); +} + +GVArrayCommon::GVArrayCommon(GVArrayCommon &&other) noexcept : storage_(std::move(other.storage_)) +{ + impl_ = this->impl_from_storage(); + other.storage_.reset(); + other.impl_ = nullptr; +} + +GVArrayCommon::GVArrayCommon(const GVArrayImpl *impl) : impl_(impl) +{ + storage_ = impl_; +} + +GVArrayCommon::GVArrayCommon(std::shared_ptr<const GVArrayImpl> impl) : impl_(impl.get()) +{ + if (impl) { + storage_ = std::move(impl); + } +} + +GVArrayCommon::~GVArrayCommon() = default; + +void GVArrayCommon::materialize(void *dst) const +{ + this->materialize(IndexMask(impl_->size()), dst); +} + +void GVArrayCommon::materialize(const IndexMask mask, void *dst) const +{ + impl_->materialize(mask, dst); +} + +void GVArrayCommon::materialize_to_uninitialized(void *dst) const +{ + this->materialize_to_uninitialized(IndexMask(impl_->size()), dst); +} + +void GVArrayCommon::materialize_to_uninitialized(const IndexMask mask, void *dst) const +{ + BLI_assert(mask.min_array_size() <= impl_->size()); + impl_->materialize_to_uninitialized(mask, dst); +} + +bool GVArrayCommon::may_have_ownership() const +{ + return impl_->may_have_ownership(); +} + +void GVArrayCommon::copy_from(const GVArrayCommon &other) +{ + if (this == &other) { + return; + } + storage_ = other.storage_; + impl_ = this->impl_from_storage(); +} + +void GVArrayCommon::move_from(GVArrayCommon &&other) noexcept +{ + if (this == &other) { + return; + } + storage_ = std::move(other.storage_); + impl_ = this->impl_from_storage(); + other.storage_.reset(); + other.impl_ = nullptr; +} + +bool GVArrayCommon::is_span() const +{ + return impl_->is_span(); +} + +GSpan GVArrayCommon::get_internal_span() const +{ + BLI_assert(this->is_span()); + return impl_->get_internal_span(); +} + +bool GVArrayCommon::is_single() const +{ + return impl_->is_single(); +} + +void GVArrayCommon::get_internal_single(void *r_value) const +{ + BLI_assert(this->is_single()); + impl_->get_internal_single(r_value); +} + +void GVArrayCommon::get_internal_single_to_uninitialized(void *r_value) const +{ + impl_->type().default_construct(r_value); + this->get_internal_single(r_value); +} + +const GVArrayImpl *GVArrayCommon::impl_from_storage() const +{ + return storage_.extra_info().get_varray(storage_.get()); +} + +IndexRange GVArrayCommon::index_range() const +{ + return IndexRange(this->size()); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #GVArray + * \{ */ + +GVArray::GVArray(const GVArray &other) = default; + +GVArray::GVArray(GVArray &&other) noexcept = default; + +GVArray::GVArray(const GVArrayImpl *impl) : GVArrayCommon(impl) +{ +} + +GVArray::GVArray(std::shared_ptr<const GVArrayImpl> impl) : GVArrayCommon(std::move(impl)) +{ +} + +GVArray GVArray::ForSingle(const CPPType &type, const int64_t size, const void *value) +{ + if (type.is_trivial() && type.size() <= 16 && type.alignment() <= 8) { + return GVArray::For<GVArrayImpl_For_SmallTrivialSingleValue<16>>(type, size, value); + } + return GVArray::For<GVArrayImpl_For_SingleValue>(type, size, value); +} + +GVArray GVArray::ForSingleRef(const CPPType &type, const int64_t size, const void *value) +{ + return GVArray::For<GVArrayImpl_For_SingleValueRef_final>(type, size, value); +} + +GVArray GVArray::ForSingleDefault(const CPPType &type, const int64_t size) +{ + return GVArray::ForSingleRef(type, size, type.default_value()); +} + +GVArray GVArray::ForSpan(GSpan span) +{ + /* Use const-cast because the underlying virtual array implementation is shared between const + * and non const data. */ + GMutableSpan mutable_span{span.type(), const_cast<void *>(span.data()), span.size()}; + return GVArray::For<GVArrayImpl_For_GSpan_final>(mutable_span); +} + +class GVArrayImpl_For_GArray : public GVArrayImpl_For_GSpan { + protected: + GArray<> array_; + + public: + GVArrayImpl_For_GArray(GArray<> array) + : GVArrayImpl_For_GSpan(array.as_mutable_span()), array_(std::move(array)) + { + } +}; + +GVArray GVArray::ForGArray(GArray<> array) +{ + return GVArray::For<GVArrayImpl_For_GArray>(array); +} + +GVArray GVArray::ForEmpty(const CPPType &type) +{ + return GVArray::ForSpan(GSpan(type)); +} + +GVArray GVArray::slice(IndexRange slice) const +{ + return GVArray::For<GVArrayImpl_For_SlicedGVArray>(*this, slice); +} + +GVArray &GVArray::operator=(const GVArray &other) +{ + this->copy_from(other); + return *this; +} + +GVArray &GVArray::operator=(GVArray &&other) noexcept +{ + this->move_from(std::move(other)); + return *this; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #GVMutableArray + * \{ */ + +GVMutableArray::GVMutableArray(const GVMutableArray &other) = default; +GVMutableArray::GVMutableArray(GVMutableArray &&other) noexcept = default; + +GVMutableArray::GVMutableArray(GVMutableArrayImpl *impl) : GVArrayCommon(impl) +{ +} + +GVMutableArray::GVMutableArray(std::shared_ptr<GVMutableArrayImpl> impl) + : GVArrayCommon(std::move(impl)) +{ +} + +GVMutableArray GVMutableArray::ForSpan(GMutableSpan span) +{ + return GVMutableArray::For<GVArrayImpl_For_GSpan_final>(span); +} + +GVMutableArray::operator GVArray() const & +{ + GVArray varray; + varray.copy_from(*this); + return varray; +} + +GVMutableArray::operator GVArray() &&noexcept +{ + GVArray varray; + varray.move_from(std::move(*this)); + return varray; +} + +GVMutableArray &GVMutableArray::operator=(const GVMutableArray &other) +{ + this->copy_from(other); + return *this; +} + +GVMutableArray &GVMutableArray::operator=(GVMutableArray &&other) noexcept +{ + this->move_from(std::move(other)); + return *this; +} + +GVMutableArrayImpl *GVMutableArray::get_implementation() const +{ + return this->get_impl(); +} + +void GVMutableArray::set_all(const void *src) +{ + this->get_impl()->set_all(src); +} + +GMutableSpan GVMutableArray::get_internal_span() const +{ + BLI_assert(this->is_span()); + const GSpan span = impl_->get_internal_span(); + return GMutableSpan(span.type(), const_cast<void *>(span.data()), span.size()); +} + +/** \} */ + +} // namespace blender diff --git a/source/blender/blenlib/intern/generic_virtual_vector_array.cc b/source/blender/blenlib/intern/generic_virtual_vector_array.cc new file mode 100644 index 00000000000..8fd1fb50b72 --- /dev/null +++ b/source/blender/blenlib/intern/generic_virtual_vector_array.cc @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_generic_virtual_vector_array.hh" + +namespace blender { + +void GVArray_For_GVVectorArrayIndex::get(const int64_t index_in_vector, void *r_value) const +{ + vector_array_.get_vector_element(index_, index_in_vector, r_value); +} + +void GVArray_For_GVVectorArrayIndex::get_to_uninitialized(const int64_t index_in_vector, + void *r_value) const +{ + type_->default_construct(r_value); + vector_array_.get_vector_element(index_, index_in_vector, r_value); +} + +int64_t GVVectorArray_For_SingleGVArray::get_vector_size_impl(const int64_t UNUSED(index)) const +{ + return varray_.size(); +} + +void GVVectorArray_For_SingleGVArray::get_vector_element_impl(const int64_t UNUSED(index), + const int64_t index_in_vector, + void *r_value) const +{ + varray_.get(index_in_vector, r_value); +} + +bool GVVectorArray_For_SingleGVArray::is_single_vector_impl() const +{ + return true; +} + +int64_t GVVectorArray_For_SingleGSpan::get_vector_size_impl(const int64_t UNUSED(index)) const +{ + return span_.size(); +} + +void GVVectorArray_For_SingleGSpan::get_vector_element_impl(const int64_t UNUSED(index), + const int64_t index_in_vector, + void *r_value) const +{ + type_->copy_assign(span_[index_in_vector], r_value); +} + +bool GVVectorArray_For_SingleGSpan::is_single_vector_impl() const +{ + return true; +} + +} // namespace blender diff --git a/source/blender/blenlib/tests/BLI_generic_array_test.cc b/source/blender/blenlib/tests/BLI_generic_array_test.cc new file mode 100644 index 00000000000..52bc7728a6a --- /dev/null +++ b/source/blender/blenlib/tests/BLI_generic_array_test.cc @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#include "testing/testing.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_array.hh" +#include "BLI_generic_array.hh" + +namespace blender::tests { + +TEST(generic_array, TypeConstructor) +{ + GArray array(CPPType::get<float>()); + EXPECT_TRUE(array.data() == nullptr); + EXPECT_EQ(array.size(), 0); + EXPECT_EQ(array.as_span().typed<float>().size(), 0); + EXPECT_TRUE(array.is_empty()); +} + +TEST(generic_array, MoveConstructor) +{ + GArray array_a(CPPType::get<int32_t>(), (int64_t)10); + GMutableSpan span_a = array_a.as_mutable_span(); + MutableSpan<int32_t> typed_span_a = span_a.typed<int32_t>(); + typed_span_a.fill(42); + + const GArray array_b = std::move(array_a); + Span<int32_t> typed_span_b = array_b.as_span().typed<int32_t>(); + EXPECT_FALSE(array_b.data() == nullptr); + EXPECT_EQ(array_b.size(), 10); + EXPECT_EQ(typed_span_b[4], 42); + + /* Make sure the copy constructor cleaned up the original, but it shouldn't clear the type. */ + EXPECT_TRUE(array_a.data() == nullptr); /* NOLINT: bugprone-use-after-move */ + EXPECT_EQ(array_a.size(), 0); /* NOLINT: bugprone-use-after-move */ + EXPECT_TRUE(array_a.is_empty()); /* NOLINT: bugprone-use-after-move */ + EXPECT_EQ(array_b.type(), array_a.type()); /* NOLINT: bugprone-use-after-move */ +} + +TEST(generic_array, CopyConstructor) +{ + GArray array_a(CPPType::get<int32_t>(), (int64_t)10); + GMutableSpan span_a = array_a.as_mutable_span(); + MutableSpan<int32_t> typed_span_a = span_a.typed<int32_t>(); + typed_span_a.fill(42); + + /* From span directly. */ + const GArray array_b = array_a.as_span(); + Span<int32_t> typed_span_b = array_b.as_span().typed<int32_t>(); + EXPECT_FALSE(array_b.data() == nullptr); + EXPECT_EQ(array_b.size(), 10); + EXPECT_EQ(typed_span_b[4], 42); + EXPECT_FALSE(array_a.is_empty()); + + /* From array. */ + const GArray array_c = array_a; + Span<int32_t> typed_span_c = array_c.as_span().typed<int32_t>(); + EXPECT_FALSE(array_c.data() == nullptr); + EXPECT_EQ(array_c.size(), 10); + EXPECT_EQ(typed_span_c[4], 42); + EXPECT_FALSE(array_a.is_empty()); +} + +TEST(generic_array, BufferAndSizeConstructor) +{ + int32_t *values = (int32_t *)MEM_malloc_arrayN(12, sizeof(int32_t), __func__); + void *buffer = (void *)values; + GArray array(CPPType::get<int32_t>(), buffer, 4); + EXPECT_FALSE(array.data() == nullptr); + EXPECT_EQ(array.size(), 4); + EXPECT_FALSE(array.is_empty()); + EXPECT_EQ(array.as_span().typed<int>().size(), 4); + EXPECT_EQ(array[0], &values[0]); + EXPECT_EQ(array[1], &values[1]); + EXPECT_EQ(array[2], &values[2]); + EXPECT_EQ(array[3], &values[3]); +} + +TEST(generic_array, Reinitialize) +{ + GArray array(CPPType::get<int32_t>(), (int64_t)5); + EXPECT_FALSE(array.data() == nullptr); + GMutableSpan span = array.as_mutable_span(); + MutableSpan<int32_t> typed_span = span.typed<int32_t>(); + typed_span.fill(77); + EXPECT_FALSE(typed_span.data() == nullptr); + typed_span[2] = 8; + EXPECT_EQ(array[2], &typed_span[2]); + EXPECT_EQ(typed_span[0], 77); + EXPECT_EQ(typed_span[1], 77); + + array.reinitialize(10); + EXPECT_EQ(array.size(), 10); + span = array.as_mutable_span(); + EXPECT_EQ(span.size(), 10); + + typed_span = span.typed<int32_t>(); + EXPECT_FALSE(typed_span.data() == nullptr); + + array.reinitialize(0); + EXPECT_EQ(array.size(), 0); +} + +TEST(generic_array, InContainer) +{ + blender::Array<GArray<>> arrays; + for (GArray<> &array : arrays) { + array = GArray(CPPType::get<int32_t>(), (int64_t)5); + array.as_mutable_span().typed<int32_t>().fill(55); + } + for (GArray<> &array : arrays) { + EXPECT_EQ(array.as_span().typed<int32_t>()[3], 55); + } +} + +} // namespace blender::tests diff --git a/source/blender/blenlib/tests/BLI_generic_span_test.cc b/source/blender/blenlib/tests/BLI_generic_span_test.cc new file mode 100644 index 00000000000..fe07a67d63b --- /dev/null +++ b/source/blender/blenlib/tests/BLI_generic_span_test.cc @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#include "testing/testing.h" + +#include "BLI_generic_span.hh" + +namespace blender::tests { + +TEST(generic_span, TypeConstructor) +{ + GSpan span(CPPType::get<float>()); + EXPECT_EQ(span.size(), 0); + EXPECT_EQ(span.typed<float>().size(), 0); + EXPECT_TRUE(span.is_empty()); +} + +TEST(generic_span, BufferAndSizeConstructor) +{ + int values[4] = {6, 7, 3, 2}; + void *buffer = (void *)values; + GSpan span(CPPType::get<int32_t>(), buffer, 4); + EXPECT_EQ(span.size(), 4); + EXPECT_FALSE(span.is_empty()); + EXPECT_EQ(span.typed<int>().size(), 4); + EXPECT_EQ(span[0], &values[0]); + EXPECT_EQ(span[1], &values[1]); + EXPECT_EQ(span[2], &values[2]); + EXPECT_EQ(span[3], &values[3]); +} + +TEST(generic_mutable_span, TypeConstructor) +{ + GMutableSpan span(CPPType::get<int32_t>()); + EXPECT_EQ(span.size(), 0); + EXPECT_TRUE(span.is_empty()); +} + +TEST(generic_mutable_span, BufferAndSizeConstructor) +{ + int values[4] = {4, 7, 3, 5}; + void *buffer = (void *)values; + GMutableSpan span(CPPType::get<int32_t>(), buffer, 4); + EXPECT_EQ(span.size(), 4); + EXPECT_FALSE(span.is_empty()); + EXPECT_EQ(span.typed<int>().size(), 4); + EXPECT_EQ(values[2], 3); + *(int *)span[2] = 10; + EXPECT_EQ(values[2], 10); + span.typed<int>()[2] = 20; + EXPECT_EQ(values[2], 20); +} + +} // namespace blender::tests diff --git a/source/blender/blenlib/tests/BLI_generic_vector_array_test.cc b/source/blender/blenlib/tests/BLI_generic_vector_array_test.cc new file mode 100644 index 00000000000..105f3603914 --- /dev/null +++ b/source/blender/blenlib/tests/BLI_generic_vector_array_test.cc @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#include "testing/testing.h" + +#include "BLI_generic_vector_array.hh" + +namespace blender::tests { + +TEST(generic_vector_array, Construct) +{ + GVectorArray vector_array{CPPType::get<int>(), 4}; + EXPECT_EQ(vector_array.size(), 4); + EXPECT_FALSE(vector_array.is_empty()); +} + +TEST(generic_vector_array, Append) +{ + GVectorArray vector_array{CPPType::get<int>(), 3}; + int value1 = 2; + vector_array.append(1, &value1); + vector_array.append(1, &value1); + int value2 = 3; + vector_array.append(0, &value2); + vector_array.append(1, &value2); + + EXPECT_EQ(vector_array[0].size(), 1); + EXPECT_EQ(vector_array[1].size(), 3); + EXPECT_EQ(vector_array[2].size(), 0); +} + +TEST(generic_vector_array, Extend) +{ + GVectorArray vector_array{CPPType::get<int>(), 3}; + vector_array.extend(0, Span<int>({1, 4, 6, 4})); + vector_array.extend(1, Span<int>()); + vector_array.extend(0, Span<int>({10, 20, 30})); + + EXPECT_EQ(vector_array[0].size(), 7); + EXPECT_EQ(vector_array[1].size(), 0); + EXPECT_EQ(vector_array[2].size(), 0); +} + +} // namespace blender::tests |