From d4c868da9f97a06c3457b8eafd344a23ed704874 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 16 Nov 2021 10:15:51 +0100 Subject: Geometry Nodes: refactor virtual array system Goals of this refactor: * Simplify creating virtual arrays. * Simplify passing virtual arrays around. * Simplify converting between typed and generic virtual arrays. * Reduce memory allocations. As a quick reminder, a virtual arrays is a data structure that behaves like an array (i.e. it can be accessed using an index). However, it may not actually be stored as array internally. The two most important implementations of virtual arrays are those that correspond to an actual plain array and those that have the same value for every index. However, many more implementations exist for various reasons (interfacing with legacy attributes, unified iterator over all points in multiple splines, ...). With this refactor the core types (`VArray`, `GVArray`, `VMutableArray` and `GVMutableArray`) can be used like "normal values". They typically live on the stack. Before, they were usually inside a `std::unique_ptr`. This makes passing them around much easier. Creation of new virtual arrays is also much simpler now due to some constructors. Memory allocations are reduced by making use of small object optimization inside the core types. Previously, `VArray` was a class with virtual methods that had to be overridden to change the behavior of a the virtual array. Now,`VArray` has a fixed size and has no virtual methods. Instead it contains a `VArrayImpl` that is similar to the old `VArray`. `VArrayImpl` should rarely ever be used directly, unless a new virtual array implementation is added. To support the small object optimization for many `VArrayImpl` classes, a new `blender::Any` type is added. It is similar to `std::any` with two additional features. It has an adjustable inline buffer size and alignment. The inline buffer size of `std::any` can't be relied on and is usually too small for our use case here. Furthermore, `blender::Any` can store additional user-defined type information without increasing the stack size. Differential Revision: https://developer.blender.org/D12986 --- source/blender/blenlib/BLI_any.hh | 319 ++++++ source/blender/blenlib/BLI_virtual_array.hh | 1078 +++++++++++++++----- source/blender/blenlib/CMakeLists.txt | 2 + source/blender/blenlib/tests/BLI_any_test.cc | 108 ++ .../blenlib/tests/BLI_virtual_array_test.cc | 55 +- 5 files changed, 1270 insertions(+), 292 deletions(-) create mode 100644 source/blender/blenlib/BLI_any.hh create mode 100644 source/blender/blenlib/tests/BLI_any_test.cc (limited to 'source/blender/blenlib') diff --git a/source/blender/blenlib/BLI_any.hh b/source/blender/blenlib/BLI_any.hh new file mode 100644 index 00000000000..0fc5de5540f --- /dev/null +++ b/source/blender/blenlib/BLI_any.hh @@ -0,0 +1,319 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +/** \file + * \ingroup bli + * + * A #blender::Any is a type-safe container for single values of any copy constructible type. + * It is similar to #std::any but provides the following two additional features: + * - Adjustable inline buffer capacity and alignment. #std::any has a small inline buffer in most + * implementations as well, but its size is not guaranteed. + * - Can store additional user-defined type information without increasing the stack size of #Any. + */ + +#include +#include + +#include "BLI_memory_utils.hh" + +namespace blender { + +namespace detail { + +/** + * Contains function pointers that manage the memory in an #Any. + * Additional type specific #ExtraInfo can be embedded here as well. + */ +template struct AnyTypeInfo { + void (*copy_construct)(void *dst, const void *src); + void (*move_construct)(void *dst, void *src); + void (*destruct)(void *src); + const void *(*get)(const void *src); + ExtraInfo extra_info; + + /** + * Used when #T is stored directly in the inline buffer of the #Any. + */ + template static const AnyTypeInfo &get_for_inline() + { + static AnyTypeInfo funcs = {[](void *dst, const void *src) { new (dst) T(*(const T *)src); }, + [](void *dst, void *src) { new (dst) T(std::move(*(T *)src)); }, + [](void *src) { ((T *)src)->~T(); }, + [](const void *src) { return src; }, + ExtraInfo::template get()}; + return funcs; + } + + /** + * Used when #T can't be stored directly in the inline buffer and is stored in a #std::unique_ptr + * instead. In this scenario, the #std::unique_ptr is stored in the inline buffer. + */ + template static const AnyTypeInfo &get_for_unique_ptr() + { + using Ptr = std::unique_ptr; + static AnyTypeInfo funcs = { + [](void *dst, const void *src) { new (dst) Ptr(new T(**(const Ptr *)src)); }, + [](void *dst, void *src) { new (dst) Ptr(new T(std::move(**(Ptr *)src))); }, + [](void *src) { ((Ptr *)src)->~Ptr(); }, + [](const void *src) -> const void * { return &**(const Ptr *)src; }, + ExtraInfo::template get()}; + return funcs; + } + + /** + * Used when the #Any does not contain any type currently. + */ + static const AnyTypeInfo &get_for_empty() + { + static AnyTypeInfo funcs = {[](void *UNUSED(dst), const void *UNUSED(src)) {}, + [](void *UNUSED(dst), void *UNUSED(src)) {}, + [](void *UNUSED(src)) {}, + [](const void *UNUSED(src)) -> const void * { return nullptr; }, + ExtraInfo{}}; + return funcs; + } +}; + +/** + * Dummy extra info that is used when no additional type information should be stored in the #Any. + */ +struct NoExtraInfo { + template static NoExtraInfo get() + { + return {}; + } +}; + +} // namespace detail + +template< + /** + * Either void or a struct that contains data members for additional type information. + * The struct has to have a static `ExtraInfo get()` method that initializes the struct + * based on a type. + */ + typename ExtraInfo = void, + /** + * Size of the inline buffer. This allows types that are small enough to be stored directly + * inside the #Any without an additional allocation. + */ + size_t InlineBufferCapacity = 8, + /** + * Required minimum alignment of the inline buffer. If this is smaller than the alignment + * requirement of a used type, a separate allocation is necessary. + */ + size_t Alignment = 8> +class Any { + private: + /* Makes it possible to use void in the template parameters. */ + using RealExtraInfo = + std::conditional_t, detail::NoExtraInfo, ExtraInfo>; + using Info = detail::AnyTypeInfo; + + /** + * Inline buffer that either contains nothing, the stored value directly, or a #std::unique_ptr + * to the value. + */ + AlignedBuffer)), Alignment> buffer_{}; + + /** + * Information about the type that is currently stored. + */ + const Info *info_ = &Info::get_for_empty(); + + public: + /** Only copy constructible types can be stored in #Any. */ + template static constexpr inline bool is_allowed_v = std::is_copy_constructible_v; + + /** + * Checks if the type will be stored in the inline buffer or if it requires a separate + * allocation. + */ + template + static constexpr inline bool is_inline_v = std::is_nothrow_move_constructible_v && + sizeof(T) <= InlineBufferCapacity && + alignof(T) <= Alignment; + + /** + * Checks if #T is the same type as this #Any, because in this case the behavior of e.g. the + * assignment operator is different. + */ + template + static constexpr inline bool is_same_any_v = std::is_same_v, Any>; + + private: + template const Info &get_info() const + { + using DecayT = std::decay_t; + static_assert(is_allowed_v); + if constexpr (is_inline_v) { + return Info::template get_for_inline(); + } + else { + return Info::template get_for_unique_ptr(); + } + } + + public: + Any() = default; + + Any(const Any &other) : info_(other.info_) + { + info_->copy_construct(&buffer_, &other.buffer_); + } + + /** + * \note The #other #Any will not be empty afterwards if it was not before. Just its value is in + * a moved-from state. + */ + Any(Any &&other) noexcept : info_(other.info_) + { + info_->move_construct(&buffer_, &other.buffer_); + } + + /** + * Constructs a new #Any that contains the given type #T from #args. The #std::in_place_type_t is + * used to disambiguate this and the copy/move constructors. + */ + template explicit Any(std::in_place_type_t, Args &&...args) + { + using DecayT = std::decay_t; + static_assert(is_allowed_v); + info_ = &this->template get_info(); + if constexpr (is_inline_v) { + /* Construct the value directly in the inline buffer. */ + new (&buffer_) DecayT(std::forward(args)...); + } + else { + /* Construct the value in a new allocation and store a #std::unique_ptr to it in the inline + * buffer. */ + new (&buffer_) std::unique_ptr(new DecayT(std::forward(args)...)); + } + } + + /** + * Constructs a new #Any that contains the given value. + */ + template, void>> + Any(T &&value) : Any(std::in_place_type, std::forward(value)) + { + } + + ~Any() + { + info_->destruct(&buffer_); + } + + /** + * \note: Only needed because the template below does not count as copy assignment operator. + */ + Any &operator=(const Any &other) + { + if (this == &other) { + return *this; + } + this->~Any(); + new (this) Any(other); + return *this; + } + + /** Assign any value to the #Any. */ + template Any &operator=(T &&other) + { + if constexpr (is_same_any_v) { + if (this == &other) { + return *this; + } + } + this->~Any(); + new (this) Any(std::forward(other)); + return *this; + } + + /** Destruct any existing value to make it empty. */ + void reset() + { + info_->destruct(&buffer_); + info_ = &Info::get_for_empty(); + } + + operator bool() const + { + return this->has_value(); + } + + bool has_value() const + { + return info_ != &Info::get_for_empty(); + } + + template std::decay_t &emplace(Args &&...args) + { + this->~Any(); + new (this) Any(std::in_place_type, std::forward(args)...); + return this->get(); + } + + /** Return true when the value that is currently stored is a #T. */ + template bool is() const + { + return info_ == &this->template get_info(); + } + + /** Get a pointer to the stored value. */ + void *get() + { + return const_cast(info_->get(&buffer_)); + } + + /** Get a pointer to the stored value. */ + const void *get() const + { + return info_->get(&buffer_); + } + + /** + * Get a reference to the stored value. This invokes undefined behavior when #T does not have the + * correct type. + */ + template std::decay_t &get() + { + BLI_assert(this->is()); + return *static_cast *>(this->get()); + } + + /** + * Get a reference to the stored value. This invokes undefined behavior when #T does not have the + * correct type. + */ + template const std::decay_t &get() const + { + BLI_assert(this->is()); + return *static_cast *>(this->get()); + } + + /** + * Get extra information that has been stored for the contained type. + */ + const RealExtraInfo &extra_info() const + { + return info_->extra_info; + } +}; + +} // namespace blender diff --git a/source/blender/blenlib/BLI_virtual_array.hh b/source/blender/blenlib/BLI_virtual_array.hh index 1c02bce8411..d9f83d3e1cb 100644 --- a/source/blender/blenlib/BLI_virtual_array.hh +++ b/source/blender/blenlib/BLI_virtual_array.hh @@ -37,148 +37,98 @@ * see of the increased compile time and binary size is worth it. */ +#include "BLI_any.hh" #include "BLI_array.hh" #include "BLI_index_mask.hh" #include "BLI_span.hh" namespace blender { -/* An immutable virtual array. */ -template class VArray { +/* 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 + * bunch of virtual methods that are wrapped by #VArray. + */ +template class VArrayImpl { protected: + /** + * Number of elements in the virtual array. All virtual arrays have a size, but in some cases it + * may make sense to set it to the max value. + */ int64_t size_; public: - VArray(const int64_t size) : size_(size) + VArrayImpl(const int64_t size) : size_(size) { BLI_assert(size_ >= 0); } - virtual ~VArray() = default; - - T get(const int64_t index) const - { - BLI_assert(index >= 0); - BLI_assert(index < size_); - return this->get_impl(index); - } + virtual ~VArrayImpl() = default; int64_t size() const { return size_; } - bool is_empty() const - { - return size_ == 0; - } - - IndexRange index_range() const - { - return IndexRange(size_); - } - - /* Returns true when the virtual array is stored as a span internally. */ - bool is_span() const - { - if (size_ == 0) { - return true; - } - return this->is_span_impl(); - } - - /* Returns the internally used span of the virtual array. This invokes undefined behavior is the - * virtual array is not stored as a span internally. */ - Span get_internal_span() const - { - BLI_assert(this->is_span()); - if (size_ == 0) { - return {}; - } - return this->get_internal_span_impl(); - } + /** + * Get the element at #index. This does not return a reference, because the value may be computed + * on the fly. + */ + virtual T get(const int64_t index) const = 0; - /* Returns true when the virtual array returns the same value for every index. */ - bool is_single() const - { - if (size_ == 1) { - return true; - } - return this->is_single_impl(); - } - - /* Returns the value that is returned for every index. This invokes undefined behavior if the - * virtual array would not return the same value for every index. */ - T get_internal_single() const - { - BLI_assert(this->is_single()); - if (size_ == 1) { - return this->get(0); - } - return this->get_internal_single_impl(); - } - - /* Get the element at a specific index. Note that this operator cannot be used to assign values - * to an index, because the return value is not a reference. */ - T operator[](const int64_t index) const - { - return this->get(index); - } - - /* Copy the entire virtual array into a span. */ - void materialize(MutableSpan r_span) const - { - this->materialize(IndexMask(size_), r_span); - } - - /* Copy some indices of the virtual array into a span. */ - void materialize(IndexMask mask, MutableSpan r_span) const - { - BLI_assert(mask.min_array_size() <= size_); - this->materialize_impl(mask, r_span); - } - - void materialize_to_uninitialized(MutableSpan r_span) const - { - this->materialize_to_uninitialized(IndexMask(size_), r_span); - } - - void materialize_to_uninitialized(IndexMask mask, MutableSpan r_span) const - { - BLI_assert(mask.min_array_size() <= size_); - this->materialize_to_uninitialized_impl(mask, r_span); - } - - protected: - virtual T get_impl(const int64_t index) const = 0; - - virtual bool is_span_impl() const + /** + * Return true when the virtual array is a plain array internally. + */ + virtual bool is_span() const { return false; } - virtual Span get_internal_span_impl() const + /** + * Return the span of the virtual array. + * This invokes undefined behavior when #is_span returned false. + */ + virtual Span get_internal_span() const { + /* Provide a default implementation, so that subclasses don't have to provide it. This method + * should never be called because #is_span returns false by default. */ BLI_assert_unreachable(); return {}; } - virtual bool is_single_impl() const + /** + * Return true when the virtual array has the same value at every index. + */ + virtual bool is_single() const { return false; } - virtual T get_internal_single_impl() const + /** + * Return the value that is used at every index. + * This invokes undefined behavior when #is_single returned false. + */ + virtual T get_internal_single() const { /* Provide a default implementation, so that subclasses don't have to provide it. This method - * should never be called because `is_single_impl` returns false by default. */ + * should never be called because #is_single returns false by default. */ BLI_assert_unreachable(); return T(); } - virtual void materialize_impl(IndexMask mask, MutableSpan r_span) const + /** + * Copy values from the virtual array into the provided span. The index of the value in the + * virtual is the same as the index in the span. + */ + virtual void materialize(IndexMask mask, MutableSpan r_span) const { T *dst = r_span.data(); + /* Optimize for a few different common cases. */ if (this->is_span()) { const T *src = this->get_internal_span().data(); mask.foreach_index([&](const int64_t i) { dst[i] = src[i]; }); @@ -192,9 +142,13 @@ template class VArray { } } - virtual void materialize_to_uninitialized_impl(IndexMask mask, MutableSpan r_span) const + /** + * Same as #materialize but #r_span is expected to be uninitialized. + */ + virtual void materialize_to_uninitialized(IndexMask mask, MutableSpan r_span) const { T *dst = r_span.data(); + /* Optimize for a few different common cases. */ if (this->is_span()) { const T *src = this->get_internal_span().data(); mask.foreach_index([&](const int64_t i) { new (dst + i) T(src[i]); }); @@ -207,43 +161,48 @@ template class VArray { mask.foreach_index([&](const int64_t i) { new (dst + i) T(this->get(i)); }); } } -}; -/* Similar to VArray, but the elements are mutable. */ -template class VMutableArray : public VArray { - public: - VMutableArray(const int64_t size) : VArray(size) + /** + * If this virtual wraps another #GVArray, this method should assign the wrapped array to the + * provided reference. This allows losslessly converting between generic and typed virtual + * 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 { + return false; } - void set(const int64_t index, T value) + /** + * Return true when this virtual array may own any of the memory it references. This can be used + * for optimization purposes when converting or copying the virtual array. + */ + virtual bool may_have_ownership() const { - BLI_assert(index >= 0); - BLI_assert(index < this->size_); - this->set_impl(index, std::move(value)); + /* Use true by default to be on the safe side. Subclasses that know for sure that they don't + * own anything can overwrite this with false. */ + return true; } +}; - /* Copy the values from the source span to all elements in the virtual array. */ - void set_all(Span src) - { - BLI_assert(src.size() == this->size_); - this->set_all_impl(src); - } +/* Similar to #VArrayImpl, but adds methods that allow modifying the referenced elements. */ +template class VMutableArrayImpl : public VArrayImpl { + public: + using VArrayImpl::VArrayImpl; - MutableSpan get_internal_span() - { - BLI_assert(this->is_span()); - Span span = static_cast *>(this)->get_internal_span(); - return MutableSpan(const_cast(span.data()), span.size()); - } + /** + * Assign the provided #value to the #index. + */ + virtual void set(const int64_t index, T value) = 0; - protected: - virtual void set_impl(const int64_t index, T value) = 0; - - virtual void set_all_impl(Span src) + /** + * Copy all elements from the provided span into the virtual array. + */ + virtual void set_all(Span src) { if (this->is_span()) { - const MutableSpan span = this->get_internal_span(); + const Span const_span = this->get_internal_span(); + const MutableSpan span{(T *)const_span.data(), const_span.size()}; initialized_copy_n(src.data(), this->size_, span.data()); } else { @@ -253,95 +212,133 @@ template class VMutableArray : public VArray { } } } -}; -template using VArrayPtr = std::unique_ptr>; -template using VMutableArrayPtr = std::unique_ptr>; + /** + * Similar to #VArrayImpl::try_assign_GVArray but for mutable virtual arrays. + */ + virtual bool try_assign_GVMutableArray(fn::GVMutableArray &UNUSED(varray)) const + { + return false; + } +}; /** * A virtual array implementation for a span. Methods in this class are final so that it can be * devirtualized by the compiler in some cases (e.g. when #devirtualize_varray is used). */ -template class VArray_For_Span : public VArray { +template class VArrayImpl_For_Span : public VArrayImpl { protected: const T *data_ = nullptr; public: - VArray_For_Span(const Span data) : VArray(data.size()), data_(data.data()) + VArrayImpl_For_Span(const Span data) : VArrayImpl(data.size()), data_(data.data()) { } protected: - VArray_For_Span(const int64_t size) : VArray(size) + VArrayImpl_For_Span(const int64_t size) : VArrayImpl(size) { } - T get_impl(const int64_t index) const final + T get(const int64_t index) const final { return data_[index]; } - bool is_span_impl() const final + bool is_span() const final { return true; } - Span get_internal_span_impl() const final + Span get_internal_span() const final { return Span(data_, this->size_); } }; -template class VMutableArray_For_MutableSpan : public VMutableArray { +/** + * A version of #VArrayImpl_For_Span that can not be subclassed. This allows safely overwriting the + * #may_have_ownership method. + */ +template class VArrayImpl_For_Span_final final : public VArrayImpl_For_Span { + public: + using VArrayImpl_For_Span::VArrayImpl_For_Span; + + private: + bool may_have_ownership() const override + { + return false; + } +}; + +/** + * Like #VArrayImpl_For_Span but for mutable data. + */ +template class VMutableArrayImpl_For_MutableSpan : public VMutableArrayImpl { protected: T *data_ = nullptr; public: - VMutableArray_For_MutableSpan(const MutableSpan data) - : VMutableArray(data.size()), data_(data.data()) + VMutableArrayImpl_For_MutableSpan(const MutableSpan data) + : VMutableArrayImpl(data.size()), data_(data.data()) { } protected: - VMutableArray_For_MutableSpan(const int64_t size) : VMutableArray(size) + VMutableArrayImpl_For_MutableSpan(const int64_t size) : VMutableArrayImpl(size) { } - T get_impl(const int64_t index) const final + T get(const int64_t index) const final { return data_[index]; } - void set_impl(const int64_t index, T value) final + void set(const int64_t index, T value) final { data_[index] = value; } - bool is_span_impl() const override + bool is_span() const override { return true; } - Span get_internal_span_impl() const override + Span get_internal_span() const override { return Span(data_, this->size_); } }; /** - * A variant of `VArray_For_Span` that owns the underlying data. + * Like #VArrayImpl_For_Span_final but for mutable data. + */ +template +class VMutableArrayImpl_For_MutableSpan_final final : public VMutableArrayImpl_For_MutableSpan { + public: + using VMutableArrayImpl_For_MutableSpan::VMutableArrayImpl_For_MutableSpan; + + private: + bool may_have_ownership() const override + { + return false; + } +}; + +/** + * A variant of `VArrayImpl_For_Span` that owns the underlying data. * The `Container` type has to implement a `size()` and `data()` method. * The `data()` method has to return a pointer to the first element in the continuous array of * elements. */ template -class VArray_For_ArrayContainer : public VArray_For_Span { +class VArrayImpl_For_ArrayContainer : public VArrayImpl_For_Span { private: Container container_; public: - VArray_For_ArrayContainer(Container container) - : VArray_For_Span((int64_t)container.size()), container_(std::move(container)) + VArrayImpl_For_ArrayContainer(Container container) + : VArrayImpl_For_Span((int64_t)container.size()), container_(std::move(container)) { this->data_ = container_.data(); } @@ -352,42 +349,670 @@ class VArray_For_ArrayContainer : public VArray_For_Span { * so that it can be devirtualized by the compiler in some cases (e.g. when #devirtualize_varray is * used). */ -template class VArray_For_Single final : public VArray { +template class VArrayImpl_For_Single final : public VArrayImpl { private: T value_; public: - VArray_For_Single(T value, const int64_t size) : VArray(size), value_(std::move(value)) + VArrayImpl_For_Single(T value, const int64_t size) + : VArrayImpl(size), value_(std::move(value)) { } protected: - T get_impl(const int64_t UNUSED(index)) const override + T get(const int64_t UNUSED(index)) const override { return value_; } - bool is_span_impl() const override + bool is_span() const override { return this->size_ == 1; } - Span get_internal_span_impl() const override + Span get_internal_span() const override { return Span(&value_, 1); } - bool is_single_impl() const override + bool is_single() const override { return true; } - T get_internal_single_impl() const override + T get_internal_single() const override { return value_; } }; +/** + * This class makes it easy to create a virtual array for an existing function or lambda. The + * `GetFunc` should take a single `index` argument and return the value at that index. + */ +template class VArrayImpl_For_Func final : public VArrayImpl { + private: + GetFunc get_func_; + + public: + VArrayImpl_For_Func(const int64_t size, GetFunc get_func) + : VArrayImpl(size), get_func_(std::move(get_func)) + { + } + + private: + T get(const int64_t index) const override + { + return get_func_(index); + } + + void materialize(IndexMask mask, MutableSpan r_span) const override + { + T *dst = r_span.data(); + mask.foreach_index([&](const int64_t i) { dst[i] = get_func_(i); }); + } + + void materialize_to_uninitialized(IndexMask mask, MutableSpan r_span) const override + { + T *dst = r_span.data(); + mask.foreach_index([&](const int64_t i) { new (dst + i) T(get_func_(i)); }); + } +}; + +/** + * \note: This is `final` so that #may_have_ownership can be implemented reliably. + */ +template +class VArrayImpl_For_DerivedSpan final : public VArrayImpl { + private: + const StructT *data_; + + public: + VArrayImpl_For_DerivedSpan(const Span data) + : VArrayImpl(data.size()), data_(data.data()) + { + } + + private: + ElemT get(const int64_t index) const override + { + return GetFunc(data_[index]); + } + + void materialize(IndexMask mask, MutableSpan r_span) const override + { + ElemT *dst = r_span.data(); + mask.foreach_index([&](const int64_t i) { dst[i] = GetFunc(data_[i]); }); + } + + void materialize_to_uninitialized(IndexMask mask, MutableSpan r_span) const override + { + ElemT *dst = r_span.data(); + mask.foreach_index([&](const int64_t i) { new (dst + i) ElemT(GetFunc(data_[i])); }); + } + + bool may_have_ownership() const override + { + return false; + } +}; + +/** + * \note: This is `final` so that #may_have_ownership can be implemented reliably. + */ +template +class VMutableArrayImpl_For_DerivedSpan final : public VMutableArrayImpl { + private: + StructT *data_; + + public: + VMutableArrayImpl_For_DerivedSpan(const MutableSpan data) + : VMutableArrayImpl(data.size()), data_(data.data()) + { + } + + private: + ElemT get(const int64_t index) const override + { + return GetFunc(data_[index]); + } + + void set(const int64_t index, ElemT value) override + { + SetFunc(data_[index], std::move(value)); + } + + void materialize(IndexMask mask, MutableSpan r_span) const override + { + ElemT *dst = r_span.data(); + mask.foreach_index([&](const int64_t i) { dst[i] = GetFunc(data_[i]); }); + } + + void materialize_to_uninitialized(IndexMask mask, MutableSpan r_span) const override + { + ElemT *dst = r_span.data(); + mask.foreach_index([&](const int64_t i) { new (dst + i) ElemT(GetFunc(data_[i])); }); + } + + bool may_have_ownership() const override + { + return false; + } +}; + +namespace detail { + +/** + * Struct that can be passed as `ExtraInfo` into an #Any. + * This struct is only intended to be used by #VArrayCommon. + */ +template struct VArrayAnyExtraInfo { + /** + * Gets the virtual array that is stored at the given pointer. + */ + const VArrayImpl *(*get_varray)(const void *buffer) = + [](const void *UNUSED(buffer)) -> const VArrayImpl * { return nullptr; }; + + template static VArrayAnyExtraInfo get() + { + /* These are the only allowed types in the #Any. */ + static_assert(std::is_base_of_v, StorageT> || + std::is_same_v *> || + std::is_same_v>>); + + /* Depending on how the virtual array implementation is stored in the #Any, a different + * #get_varray function is required. */ + if constexpr (std::is_base_of_v, StorageT>) { + return {[](const void *buffer) { + return static_cast *>((const StorageT *)buffer); + }}; + } + else if constexpr (std::is_same_v *>) { + return {[](const void *buffer) { return *(const StorageT *)buffer; }}; + } + else if constexpr (std::is_same_v>>) { + return {[](const void *buffer) { return ((const StorageT *)buffer)->get(); }}; + } + else { + BLI_assert_unreachable(); + return {}; + } + } +}; + +} // namespace detail + +/** + * Utility class to reduce code duplication for methods available on #VArray and #VMutableArray. + * Deriving #VMutableArray from #VArray would have some issues: + * - Static methods on #VArray would also be available on #VMutableArray. + * - It would allow assigning a #VArray to a #VMutableArray under some circumstances which is not + * allowed and could result in hard to find bugs. + */ +template class VArrayCommon { + protected: + /** + * Store the virtual array implementation in an #Any. This makes it easy to avoid a memory + * allocation if the implementation is small enough and is copyable. This is the case for the + * most common virtual arrays. + * Other virtual array implementations are typically stored as #std::shared_ptr. That works even + * when the implementation itself is not copyable and makes copying #VArrayCommon cheaper. + */ + using Storage = Any, 24, 8>; + + /** + * Pointer to the currently contained virtual array implementation. This is allowed to be null. + */ + const VArrayImpl *impl_ = nullptr; + /** + * Does the memory management for the virtual array implementation. It contains one of the + * following: + * - Inlined subclass of #VArrayImpl. + * - Non-owning pointer to a #VArrayImpl. + * - Shared pointer to a #VArrayImpl. + */ + Storage storage_; + + protected: + VArrayCommon() = default; + + /** Copy constructor. */ + VArrayCommon(const VArrayCommon &other) : storage_(other.storage_) + { + impl_ = this->impl_from_storage(); + } + + /** Move constructor. */ + VArrayCommon(VArrayCommon &&other) noexcept : storage_(std::move(other.storage_)) + { + impl_ = this->impl_from_storage(); + other.storage_.reset(); + other.impl_ = nullptr; + } + + /** + * Wrap an existing #VArrayImpl and don't take ownership of it. This should rarely be used in + * practice. + */ + VArrayCommon(const VArrayImpl *impl) : impl_(impl) + { + storage_ = impl_; + } + + /** + * Wrap an existing #VArrayImpl that is contained in a #std::shared_ptr. This takes ownership. + */ + VArrayCommon(std::shared_ptr> impl) : impl_(impl.get()) + { + if (impl) { + storage_ = std::move(impl); + } + } + + /** + * Replace the contained #VArrayImpl. + */ + template void emplace(Args &&...args) + { + /* Make sure we are actually constructing a #VArrayImpl. */ + static_assert(std::is_base_of_v, ImplT>); + if constexpr (std::is_copy_constructible_v && Storage::template is_inline_v) { + /* Only inline the implementatiton when it is copyable and when it fits into the inline + * buffer of the storage. */ + impl_ = &storage_.template emplace(std::forward(args)...); + } + else { + /* If it can't be inlined, create a new #std::shared_ptr instead and store that in the + * storage. */ + std::shared_ptr> ptr = std::make_shared( + std::forward(args)...); + impl_ = &*ptr; + storage_ = std::move(ptr); + } + } + + /** Utility to implement a copy assignment operator in a subclass. */ + void copy_from(const VArrayCommon &other) + { + if (this == &other) { + return; + } + storage_ = other.storage_; + impl_ = this->impl_from_storage(); + } + + /** Utility to implement a move assignment operator in a subclass. */ + void move_from(VArrayCommon &&other) noexcept + { + if (this == &other) { + return; + } + storage_ = std::move(other.storage_); + impl_ = this->impl_from_storage(); + other.storage_.reset(); + other.impl_ = nullptr; + } + + /** Get a pointer to the virtual array implementation that is currently stored in #storage_, or + * null. */ + const VArrayImpl *impl_from_storage() const + { + return storage_.extra_info().get_varray(storage_.get()); + } + + public: + /** Return false when there is no virtual array implementation currently. */ + operator bool() const + { + return impl_ != nullptr; + } + + /** + * Get the element at a specific index. + * \note: This can't return a reference because the value may be computed on the fly. This also + * implies that one can not use this method for assignments. + */ + T operator[](const int64_t index) const + { + BLI_assert(*this); + BLI_assert(index >= 0); + BLI_assert(index < this->size()); + return impl_->get(index); + } + + /** + * Same as the #operator[] but is sometimes easier to use when one has a pointer to a virtual + * array. + */ + T get(const int64_t index) const + { + return (*this)[index]; + } + + /** + * Return the size of the virtual array. It's allowed to call this method even when there is no + * virtual array. In this case 0 is returned. + */ + int64_t size() const + { + if (impl_ == nullptr) { + return 0; + } + return impl_->size(); + } + + /** True when the size is zero or when there is no virtual array. */ + bool is_empty() const + { + return this->size() == 0; + } + + IndexRange index_range() const + { + return IndexRange(this->size()); + } + + /** Return true when the virtual array is stored as a span internally. */ + bool is_span() const + { + BLI_assert(*this); + if (this->is_empty()) { + return true; + } + return impl_->is_span(); + } + + /** + * Returns the internally used span of the virtual array. This invokes undefined behavior is the + * virtual array is not stored as a span internally. + */ + Span get_internal_span() const + { + BLI_assert(this->is_span()); + if (this->is_empty()) { + return {}; + } + return impl_->get_internal_span(); + } + + /** Return true when the virtual array returns the same value for every index. */ + bool is_single() const + { + BLI_assert(*this); + if (impl_->size() == 1) { + return true; + } + return impl_->is_single(); + } + + /** + * Return the value that is returned for every index. This invokes undefined behavior if the + * virtual array would not return the same value for every index. + */ + T get_internal_single() const + { + BLI_assert(this->is_single()); + if (impl_->size() == 1) { + return impl_->get(0); + } + return impl_->get_internal_single(); + } + + /** Copy the entire virtual array into a span. */ + void materialize(MutableSpan r_span) const + { + this->materialize(IndexMask(this->size()), r_span); + } + + /** Copy some indices of the virtual array into a span. */ + void materialize(IndexMask mask, MutableSpan r_span) const + { + BLI_assert(mask.min_array_size() <= this->size()); + impl_->materialize(mask, r_span); + } + + void materialize_to_uninitialized(MutableSpan r_span) const + { + this->materialize_to_uninitialized(IndexMask(this->size()), r_span); + } + + void materialize_to_uninitialized(IndexMask mask, MutableSpan r_span) const + { + BLI_assert(mask.min_array_size() <= this->size()); + impl_->materialize_to_uninitialized(mask, r_span); + } + + /** See #GVArrayImpl::try_assign_GVArray. */ + bool try_assign_GVArray(fn::GVArray &varray) const + { + return impl_->try_assign_GVArray(varray); + } + + /** See #GVArrayImpl::may_have_ownership. */ + bool may_have_ownership() const + { + return impl_->may_have_ownership(); + } +}; + +template class VMutableArray; + +/** + * A #VArray wraps a virtual array implementation and provides easy access to its elements. It can + * be copied and moved. While it is relatively small, it should still be passed by reference if + * possible (other than e.g. #Span). + */ +template class VArray : public VArrayCommon { + friend VMutableArray; + + public: + VArray() = default; + VArray(const VArray &other) = default; + VArray(VArray &&other) noexcept = default; + + VArray(const VArrayImpl *impl) : VArrayCommon(impl) + { + } + + VArray(std::shared_ptr> impl) : VArrayCommon(std::move(impl)) + { + } + + /** + * Construct a new virtual array for a custom #VArrayImpl. + */ + template static VArray For(Args &&...args) + { + static_assert(std::is_base_of_v, ImplT>); + VArray varray; + varray.template emplace(std::forward(args)...); + return varray; + } + + /** + * Construct a new virtual array that has the same value at every index. + */ + static VArray ForSingle(T value, const int64_t size) + { + return VArray::For>(std::move(value), size); + } + + /** + * Construct a new virtual array for an existing span. This does not take ownership of the + * underlying memory. + */ + static VArray ForSpan(Span values) + { + return VArray::For>(values); + } + + /** + * Construct a new virtual that will invoke the provided function whenever an element is + * accessed. + */ + template static VArray ForFunc(const int64_t size, GetFunc get_func) + { + return VArray::For>(size, std::move(get_func)); + } + + /** + * Construct a new virtual array for an existing span with a mapping function. This does not take + * ownership of the span. + */ + template + static VArray ForDerivedSpan(Span values) + { + return VArray::For>(values); + } + + /** + * Construct a new virtual array for an existing container. Every container that lays out the + * elements in a plain array works. This takes ownership of the passed in container. If that is + * not desired, use #ForSpan instead. + */ + template static VArray ForContainer(ContainerT container) + { + return VArray::For>(std::move(container)); + } + + VArray &operator=(const VArray &other) + { + this->copy_from(other); + return *this; + } + + VArray &operator=(VArray &&other) noexcept + { + this->move_from(std::move(other)); + return *this; + } +}; + +/** + * Similar to #VArray but references a virtual array that can be modified. + */ +template class VMutableArray : public VArrayCommon { + public: + VMutableArray() = default; + VMutableArray(const VMutableArray &other) = default; + VMutableArray(VMutableArray &&other) noexcept = default; + + VMutableArray(const VMutableArrayImpl *impl) : VArrayCommon(impl) + { + } + + VMutableArray(std::shared_ptr> impl) + : VArrayCommon(std::move(impl)) + { + } + + /** + * Construct a new virtual array for a custom #VMutableArrayImpl. + */ + template static VMutableArray For(Args &&...args) + { + static_assert(std::is_base_of_v, ImplT>); + VMutableArray varray; + varray.template emplace(std::forward(args)...); + return varray; + } + + /** + * Construct a new virtual array for an existing span. This does not take ownership of the span. + */ + static VMutableArray ForSpan(MutableSpan values) + { + return VMutableArray::For>(values); + } + + /** + * Construct a new virtual array for an existing span with a mapping function. This does not take + * ownership of the span. + */ + template + static VMutableArray ForDerivedSpan(MutableSpan values) + { + return VMutableArray::For>( + values); + } + + /** Convert to a #VArray by copying. */ + operator VArray() const & + { + VArray varray; + varray.copy_from(*this); + return varray; + } + + /** Convert to a #VArray by moving. */ + operator VArray() &&noexcept + { + VArray varray; + varray.move_from(std::move(*this)); + return varray; + } + + VMutableArray &operator=(const VMutableArray &other) + { + this->copy_from(other); + return *this; + } + + VMutableArray &operator=(VMutableArray &&other) noexcept + { + this->move_from(std::move(other)); + return *this; + } + + /** + * Get access to the internal span. This invokes undefined behavior if the #is_span returned + * false. + */ + MutableSpan get_internal_span() const + { + BLI_assert(this->is_span()); + const Span span = this->impl_->get_internal_span(); + return MutableSpan(const_cast(span.data()), span.size()); + } + + /** + * Set the value at the given index. + */ + void set(const int64_t index, T value) + { + BLI_assert(index >= 0); + BLI_assert(index < this->size()); + this->get_impl()->set(index, std::move(value)); + } + + /** + * Copy the values from the source span to all elements in the virtual array. + */ + void set_all(Span src) + { + BLI_assert(src.size() == this->size()); + this->get_impl()->set_all(src); + } + + /** See #GVMutableArrayImpl::try_assign_GVMutableArray. */ + bool try_assign_GVMutableArray(fn::GVMutableArray &varray) const + { + return this->get_impl()->try_assign_GVMutableArray(varray); + } + + private: + /** Utility to get the pointer to the wrapped #VMutableArrayImpl. */ + VMutableArrayImpl *get_impl() const + { + /* This cast is valid by the invariant that a #VMutableArray->impl_ is always a + * #VMutableArrayImpl. */ + return (VMutableArrayImpl *)this->impl_; + } +}; + /** * In many cases a virtual array is a span internally. In those cases, access to individual could * be much more efficient than calling a virtual method. When the underlying virtual array is not a @@ -401,11 +1026,11 @@ template class VArray_For_Single final : public VArray { */ template class VArray_Span final : public Span { private: - const VArray &varray_; + VArray varray_; Array owned_data_; public: - VArray_Span(const VArray &varray) : Span(), varray_(varray) + VArray_Span(VArray varray) : Span(), varray_(std::move(varray)) { this->size_ = varray_.size(); if (varray_.is_span()) { @@ -421,7 +1046,7 @@ template class VArray_Span final : public Span { }; /** - * Same as VArray_Span, but for a mutable span. + * Same as #VArray_Span, but for a mutable span. * The important thing to note is that when changing this span, the results might not be * immediately reflected in the underlying virtual array (only when the virtual array is a span * internally). The #save method can be used to write all changes to the underlying virtual array, @@ -429,7 +1054,7 @@ template class VArray_Span final : public Span { */ template class VMutableArray_Span final : public MutableSpan { private: - VMutableArray &varray_; + VMutableArray varray_; Array owned_data_; bool save_has_been_called_ = false; bool show_not_saved_warning_ = true; @@ -437,8 +1062,8 @@ template class VMutableArray_Span final : public MutableSpan { public: /* Create a span for any virtual array. This is cheap when the virtual array is a span itself. If * not, a new array has to be allocated as a wrapper for the underlying virtual array. */ - VMutableArray_Span(VMutableArray &varray, const bool copy_values_to_span = true) - : MutableSpan(), varray_(varray) + VMutableArray_Span(VMutableArray varray, const bool copy_values_to_span = true) + : MutableSpan(), varray_(std::move(varray)) { this->size_ = varray_.size(); if (varray_.is_span()) { @@ -482,106 +1107,6 @@ template class VMutableArray_Span final : public MutableSpan { } }; -/** - * This class makes it easy to create a virtual array for an existing function or lambda. The - * `GetFunc` should take a single `index` argument and return the value at that index. - */ -template class VArray_For_Func final : public VArray { - private: - GetFunc get_func_; - - public: - VArray_For_Func(const int64_t size, GetFunc get_func) - : VArray(size), get_func_(std::move(get_func)) - { - } - - private: - T get_impl(const int64_t index) const override - { - return get_func_(index); - } - - void materialize_impl(IndexMask mask, MutableSpan r_span) const override - { - T *dst = r_span.data(); - mask.foreach_index([&](const int64_t i) { dst[i] = get_func_(i); }); - } - - void materialize_to_uninitialized_impl(IndexMask mask, MutableSpan r_span) const override - { - T *dst = r_span.data(); - mask.foreach_index([&](const int64_t i) { new (dst + i) T(get_func_(i)); }); - } -}; - -template -class VArray_For_DerivedSpan : public VArray { - private: - const StructT *data_; - - public: - VArray_For_DerivedSpan(const Span data) : VArray(data.size()), data_(data.data()) - { - } - - private: - ElemT get_impl(const int64_t index) const override - { - return GetFunc(data_[index]); - } - - void materialize_impl(IndexMask mask, MutableSpan r_span) const override - { - ElemT *dst = r_span.data(); - mask.foreach_index([&](const int64_t i) { dst[i] = GetFunc(data_[i]); }); - } - - void materialize_to_uninitialized_impl(IndexMask mask, MutableSpan r_span) const override - { - ElemT *dst = r_span.data(); - mask.foreach_index([&](const int64_t i) { new (dst + i) ElemT(GetFunc(data_[i])); }); - } -}; - -template -class VMutableArray_For_DerivedSpan : public VMutableArray { - private: - StructT *data_; - - public: - VMutableArray_For_DerivedSpan(const MutableSpan data) - : VMutableArray(data.size()), data_(data.data()) - { - } - - private: - ElemT get_impl(const int64_t index) const override - { - return GetFunc(data_[index]); - } - - void set_impl(const int64_t index, ElemT value) override - { - SetFunc(data_[index], std::move(value)); - } - - void materialize_impl(IndexMask mask, MutableSpan r_span) const override - { - ElemT *dst = r_span.data(); - mask.foreach_index([&](const int64_t i) { dst[i] = GetFunc(data_[i]); }); - } - - void materialize_to_uninitialized_impl(IndexMask mask, MutableSpan r_span) const override - { - ElemT *dst = r_span.data(); - mask.foreach_index([&](const int64_t i) { new (dst + i) ElemT(GetFunc(data_[i])); }); - } -}; - /** * Generate multiple versions of the given function optimized for different virtual arrays. * One has to be careful with nesting multiple devirtualizations, because that results in an @@ -596,15 +1121,14 @@ inline void devirtualize_varray(const VArray &varray, const Func &func, bool /* Support disabling the devirtualization to simplify benchmarking. */ if (enable) { if (varray.is_single()) { - /* `VArray_For_Single` can be used for devirtualization, because it is declared `final`. */ - const VArray_For_Single varray_single{varray.get_internal_single(), varray.size()}; - func(varray_single); + /* `VArrayImpl_For_Single` can be used for devirtualization, because it is declared `final`. + */ + func(VArray::ForSingle(varray.get_internal_single(), varray.size())); return; } if (varray.is_span()) { - /* `VArray_For_Span` can be used for devirtualization, because it is declared `final`. */ - const VArray_For_Span varray_span{varray.get_internal_span()}; - func(varray_span); + /* `VArrayImpl_For_Span` can be used for devirtualization, because it is declared `final`. */ + func(VArray::ForSpan(varray.get_internal_span())); return; } } @@ -629,27 +1153,23 @@ inline void devirtualize_varray2(const VArray &varray1, const bool is_single1 = varray1.is_single(); const bool is_single2 = varray2.is_single(); if (is_span1 && is_span2) { - const VArray_For_Span varray1_span{varray1.get_internal_span()}; - const VArray_For_Span varray2_span{varray2.get_internal_span()}; - func(varray1_span, varray2_span); + func(VArray::ForSpan(varray1.get_internal_span()), + VArray::ForSpan(varray2.get_internal_span())); return; } if (is_span1 && is_single2) { - const VArray_For_Span varray1_span{varray1.get_internal_span()}; - const VArray_For_Single varray2_single{varray2.get_internal_single(), varray2.size()}; - func(varray1_span, varray2_single); + func(VArray::ForSpan(varray1.get_internal_span()), + VArray::ForSingle(varray2.get_internal_single(), varray2.size())); return; } if (is_single1 && is_span2) { - const VArray_For_Single varray1_single{varray1.get_internal_single(), varray1.size()}; - const VArray_For_Span varray2_span{varray2.get_internal_span()}; - func(varray1_single, varray2_span); + func(VArray::ForSingle(varray1.get_internal_single(), varray1.size()), + VArray::ForSpan(varray2.get_internal_span())); return; } if (is_single1 && is_single2) { - const VArray_For_Single varray1_single{varray1.get_internal_single(), varray1.size()}; - const VArray_For_Single varray2_single{varray2.get_internal_single(), varray2.size()}; - func(varray1_single, varray2_single); + func(VArray::ForSingle(varray1.get_internal_single(), varray1.size()), + VArray::ForSingle(varray2.get_internal_single(), varray2.size())); return; } } diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 7db984aef5c..29493c799b3 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -165,6 +165,7 @@ set(SRC BLI_alloca.h BLI_allocator.hh + BLI_any.hh BLI_args.h BLI_array.h BLI_array.hh @@ -411,6 +412,7 @@ blender_add_lib(bf_blenlib "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") if(WITH_GTESTS) set(TEST_SRC + tests/BLI_any_test.cc tests/BLI_array_store_test.cc tests/BLI_array_test.cc tests/BLI_array_utils_test.cc diff --git a/source/blender/blenlib/tests/BLI_any_test.cc b/source/blender/blenlib/tests/BLI_any_test.cc new file mode 100644 index 00000000000..226088cf3c7 --- /dev/null +++ b/source/blender/blenlib/tests/BLI_any_test.cc @@ -0,0 +1,108 @@ +/* Apache License, Version 2.0 */ + +#include "BLI_any.hh" +#include "BLI_map.hh" + +#include "testing/testing.h" + +namespace blender::tests { + +TEST(any, DefaultConstructor) +{ + Any a; + EXPECT_FALSE(a.has_value()); +} + +TEST(any, AssignInt) +{ + Any<> a = 5; + EXPECT_TRUE(a.has_value()); + EXPECT_TRUE(a.is()); + EXPECT_FALSE(a.is()); + const int &value = a.get(); + EXPECT_EQ(value, 5); + a = 10; + EXPECT_EQ(value, 10); + + Any b = a; + EXPECT_TRUE(b.has_value()); + EXPECT_EQ(b.get(), 10); + + Any c = std::move(a); + EXPECT_TRUE(c); + EXPECT_EQ(c.get(), 10); + + EXPECT_EQ(a.get(), 10); /* NOLINT: bugprone-use-after-move */ + + a.reset(); + EXPECT_FALSE(a); +} + +TEST(any, AssignMap) +{ + Any<> a = Map(); + EXPECT_TRUE(a.has_value()); + EXPECT_TRUE((a.is>())); + EXPECT_FALSE((a.is>())); + Map &map = a.get>(); + map.add(4, 2); + EXPECT_EQ((a.get>().lookup(4)), 2); + + Any b = a; + EXPECT_TRUE(b); + EXPECT_EQ((b.get>().lookup(4)), 2); + + Any c = std::move(a); + c = c; + EXPECT_TRUE(b); + EXPECT_EQ((c.get>().lookup(4)), 2); + + EXPECT_TRUE((a.get>().is_empty())); /* NOLINT: bugprone-use-after-move */ +} + +TEST(any, AssignAny) +{ + Any<> a = 5; + Any<> b = std::string("hello"); + Any c; + + Any z; + EXPECT_FALSE(z.has_value()); + + z = a; + EXPECT_TRUE(z.has_value()); + EXPECT_EQ(z.get(), 5); + + z = b; + EXPECT_EQ(z.get(), "hello"); + + z = c; + EXPECT_FALSE(z.has_value()); + + z = Any(std::in_place_type>, a); + EXPECT_FALSE(z.is()); + EXPECT_TRUE(z.is>()); + EXPECT_EQ(z.get>().get(), 5); +} + +struct ExtraSizeInfo { + size_t size; + + template static ExtraSizeInfo get() + { + return {sizeof(T)}; + } +}; + +TEST(any, ExtraInfo) +{ + using MyAny = Any; + + MyAny a = 5; + EXPECT_EQ(a.extra_info().size, sizeof(int)); + + a = std::string("hello"); + EXPECT_EQ(a.extra_info().size, sizeof(std::string)); +} + +} // namespace blender::tests diff --git a/source/blender/blenlib/tests/BLI_virtual_array_test.cc b/source/blender/blenlib/tests/BLI_virtual_array_test.cc index a6d2ca10315..7a548e7c434 100644 --- a/source/blender/blenlib/tests/BLI_virtual_array_test.cc +++ b/source/blender/blenlib/tests/BLI_virtual_array_test.cc @@ -12,7 +12,7 @@ namespace blender::tests { TEST(virtual_array, Span) { std::array data = {3, 4, 5, 6, 7}; - VArray_For_Span varray{data}; + VArray varray = VArray::ForSpan(data); EXPECT_EQ(varray.size(), 5); EXPECT_EQ(varray.get(0), 3); EXPECT_EQ(varray.get(4), 7); @@ -23,7 +23,7 @@ TEST(virtual_array, Span) TEST(virtual_array, Single) { - VArray_For_Single varray{10, 4}; + VArray varray = VArray::ForSingle(10, 4); EXPECT_EQ(varray.size(), 4); EXPECT_EQ(varray.get(0), 10); EXPECT_EQ(varray.get(3), 10); @@ -35,7 +35,7 @@ TEST(virtual_array, Array) { Array array = {1, 2, 3, 5, 8}; { - VArray_For_ArrayContainer varray{array}; + VArray varray = VArray::ForContainer(array); EXPECT_EQ(varray.size(), 5); EXPECT_EQ(varray[0], 1); EXPECT_EQ(varray[2], 3); @@ -43,7 +43,7 @@ TEST(virtual_array, Array) EXPECT_TRUE(varray.is_span()); } { - VArray_For_ArrayContainer varray{std::move(array)}; + VArray varray = VArray::ForContainer(std::move(array)); EXPECT_EQ(varray.size(), 5); EXPECT_EQ(varray[0], 1); EXPECT_EQ(varray[2], 3); @@ -51,7 +51,7 @@ TEST(virtual_array, Array) EXPECT_TRUE(varray.is_span()); } { - VArray_For_ArrayContainer varray{array}; /* NOLINT: bugprone-use-after-move */ + VArray varray = VArray::ForContainer(array); /* NOLINT: bugprone-use-after-move */ EXPECT_TRUE(varray.is_empty()); } } @@ -59,7 +59,7 @@ TEST(virtual_array, Array) TEST(virtual_array, Vector) { Vector vector = {9, 8, 7, 6}; - VArray_For_ArrayContainer varray{std::move(vector)}; + VArray varray = VArray::ForContainer(std::move(vector)); EXPECT_EQ(varray.size(), 4); EXPECT_EQ(varray[0], 9); EXPECT_EQ(varray[3], 6); @@ -68,7 +68,7 @@ TEST(virtual_array, Vector) TEST(virtual_array, StdVector) { std::vector vector = {5, 6, 7, 8}; - VArray_For_ArrayContainer varray{std::move(vector)}; + VArray varray = VArray::ForContainer(std::move(vector)); EXPECT_EQ(varray.size(), 4); EXPECT_EQ(varray[0], 5); EXPECT_EQ(varray[1], 6); @@ -77,7 +77,7 @@ TEST(virtual_array, StdVector) TEST(virtual_array, StdArray) { std::array array = {2, 3, 4, 5}; - VArray_For_ArrayContainer varray{array}; + VArray varray = VArray::ForContainer(std::move(array)); EXPECT_EQ(varray.size(), 4); EXPECT_EQ(varray[0], 2); EXPECT_EQ(varray[1], 3); @@ -86,7 +86,7 @@ TEST(virtual_array, StdArray) TEST(virtual_array, VectorSet) { VectorSet vector_set = {5, 3, 7, 3, 3, 5, 1}; - VArray_For_ArrayContainer varray{std::move(vector_set)}; + VArray varray = VArray::ForContainer(std::move(vector_set)); EXPECT_TRUE(vector_set.is_empty()); /* NOLINT: bugprone-use-after-move. */ EXPECT_EQ(varray.size(), 4); EXPECT_EQ(varray[0], 5); @@ -98,7 +98,7 @@ TEST(virtual_array, VectorSet) TEST(virtual_array, Func) { auto func = [](int64_t index) { return (int)(index * index); }; - VArray_For_Func varray{10, func}; + VArray varray = VArray::ForFunc(10, func); EXPECT_EQ(varray.size(), 10); EXPECT_EQ(varray[0], 0); EXPECT_EQ(varray[3], 9); @@ -108,7 +108,7 @@ TEST(virtual_array, Func) TEST(virtual_array, AsSpan) { auto func = [](int64_t index) { return (int)(10 * index); }; - VArray_For_Func func_varray{10, func}; + VArray func_varray = VArray::ForFunc(10, func); VArray_Span span_varray{func_varray}; EXPECT_EQ(span_varray.size(), 10); Span span = span_varray; @@ -134,13 +134,14 @@ TEST(virtual_array, DerivedSpan) vector.append({3, 4, 5}); vector.append({1, 1, 1}); { - VArray_For_DerivedSpan, int, get_x> varray{vector}; + VArray varray = VArray::ForDerivedSpan, get_x>(vector); EXPECT_EQ(varray.size(), 2); EXPECT_EQ(varray[0], 3); EXPECT_EQ(varray[1], 1); } { - VMutableArray_For_DerivedSpan, int, get_x, set_x> varray{vector}; + VMutableArray varray = + VMutableArray::ForDerivedSpan, get_x, set_x>(vector); EXPECT_EQ(varray.size(), 2); EXPECT_EQ(varray[0], 3); EXPECT_EQ(varray[1], 1); @@ -151,4 +152,32 @@ TEST(virtual_array, DerivedSpan) } } +TEST(virtual_array, MutableToImmutable) +{ + std::array array = {4, 2, 6, 4}; + { + VMutableArray mutable_varray = VMutableArray::ForSpan(array); + VArray varray = mutable_varray; + EXPECT_TRUE(varray.is_span()); + EXPECT_EQ(varray.size(), 4); + EXPECT_EQ(varray[1], 2); + EXPECT_EQ(mutable_varray.size(), 4); + } + { + VMutableArray mutable_varray = VMutableArray::ForSpan(array); + EXPECT_EQ(mutable_varray.size(), 4); + VArray varray = std::move(mutable_varray); + EXPECT_TRUE(varray.is_span()); + EXPECT_EQ(varray.size(), 4); + EXPECT_EQ(varray[1], 2); + EXPECT_EQ(mutable_varray.size(), 0); + } + { + VArray varray = VMutableArray::ForSpan(array); + EXPECT_TRUE(varray.is_span()); + EXPECT_EQ(varray.size(), 4); + EXPECT_EQ(varray[1], 2); + } +} + } // namespace blender::tests -- cgit v1.2.3