Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/blenlib')
-rw-r--r--source/blender/blenlib/BLI_generic_array.hh255
-rw-r--r--source/blender/blenlib/BLI_generic_pointer.hh123
-rw-r--r--source/blender/blenlib/BLI_generic_span.hh169
-rw-r--r--source/blender/blenlib/BLI_generic_value_map.hh111
-rw-r--r--source/blender/blenlib/BLI_generic_vector_array.hh147
-rw-r--r--source/blender/blenlib/BLI_generic_virtual_array.hh877
-rw-r--r--source/blender/blenlib/BLI_generic_virtual_vector_array.hh173
-rw-r--r--source/blender/blenlib/BLI_virtual_array.hh10
-rw-r--r--source/blender/blenlib/CMakeLists.txt13
-rw-r--r--source/blender/blenlib/intern/generic_vector_array.cc96
-rw-r--r--source/blender/blenlib/intern/generic_virtual_array.cc724
-rw-r--r--source/blender/blenlib/intern/generic_virtual_vector_array.cc53
-rw-r--r--source/blender/blenlib/tests/BLI_generic_array_test.cc117
-rw-r--r--source/blender/blenlib/tests/BLI_generic_span_test.cc53
-rw-r--r--source/blender/blenlib/tests/BLI_generic_vector_array_test.cc43
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