/* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once /** \file * \ingroup bli * * A virtual array is a data structure that behaves similar to an array, but its elements are * accessed through virtual methods. This improves the decoupling of a function from its callers, * because it does not have to know exactly how the data is laid out in memory, or if it is stored * in memory at all. It could just as well be computed on the fly. * * Taking a virtual array as parameter instead of a more specific non-virtual type has some * tradeoffs. Access to individual elements of the individual elements is higher due to function * call overhead. On the other hand, potential callers don't have to convert the data into the * specific format required for the function. This can be a costly conversion if only few of the * elements are accessed in the end. * * Functions taking a virtual array as input can still optimize for different data layouts. For * example, they can check if the array is stored as an array internally or if it is the same * element for all indices. Whether it is worth to optimize for different data layouts in a * function has to be decided on a case by case basis. One should always do some benchmarking to * see of the increased compile time and binary size is worth it. */ #include #include "BLI_any.hh" #include "BLI_array.hh" #include "BLI_index_mask.hh" #include "BLI_span.hh" namespace blender { /** Forward declarations for generic virtual arrays. */ class GVArray; class GVMutableArray; /** * Is used to quickly check if a varray is a span or single value. This struct also allows * retrieving multiple pieces of data with a single virtual method call. */ struct CommonVArrayInfo { enum class Type : uint8_t { /* Is not one of the common special types below. */ Any, Span, Single, }; Type type = Type::Any; /** True when the #data becomes a dangling pointer when the virtual array is destructed. */ bool may_have_ownership = true; /** * Points either to nothing, a single value or array of values, depending on #type. * If this is a span of a mutable virtual array, it is safe to cast away const. */ const void *data; CommonVArrayInfo() = default; CommonVArrayInfo(const Type _type, const bool _may_have_ownership, const void *_data) : type(_type), may_have_ownership(_may_have_ownership), data(_data) { } }; /** * 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: VArrayImpl(const int64_t size) : size_(size) { BLI_assert(size_ >= 0); } virtual ~VArrayImpl() = default; int64_t size() const { return size_; } /** * Get the element at #index. This does not return a reference, because the value may be computed * on the fly. */ virtual T get(int64_t index) const = 0; virtual CommonVArrayInfo common_info() const { return {}; } /** * Copy values from the virtual array into the provided span. The index of the value in the * virtual array is the same as the index in the span. */ virtual void materialize(IndexMask mask, MutableSpan r_span) const { mask.foreach_index([&](const int64_t i) { r_span[i] = this->get(i); }); } /** * 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(); mask.foreach_index([&](const int64_t i) { new (dst + i) T(this->get(i)); }); } /** * Copy values from the virtual array into the provided span. Contrary to #materialize, the index * in virtual array is not the same as the index in the output span. Instead, the span is filled * without gaps. */ virtual void materialize_compressed(IndexMask mask, MutableSpan r_span) const { BLI_assert(mask.size() == r_span.size()); mask.to_best_mask_type([&](auto best_mask) { for (const int64_t i : IndexRange(best_mask.size())) { r_span[i] = this->get(best_mask[i]); } }); } /** * Same as #materialize_compressed but #r_span is expected to be uninitialized. */ virtual void materialize_compressed_to_uninitialized(IndexMask mask, MutableSpan r_span) const { BLI_assert(mask.size() == r_span.size()); T *dst = r_span.data(); mask.to_best_mask_type([&](auto best_mask) { for (const int64_t i : IndexRange(best_mask.size())) { new (dst + i) T(this->get(best_mask[i])); } }); } /** * 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(GVArray & /*varray*/) const { return false; } /** * Return true when the other virtual array should be considered to be the same, e.g. because it * shares the same underlying memory. */ virtual bool is_same(const VArrayImpl & /*other*/) const { return false; } }; /** Similar to #VArrayImpl, but adds methods that allow modifying the referenced elements. */ template class VMutableArrayImpl : public VArrayImpl { public: using VArrayImpl::VArrayImpl; /** * Assign the provided #value to the #index. */ virtual void set(int64_t index, T value) = 0; /** * Copy all elements from the provided span into the virtual array. */ virtual void set_all(Span src) { const CommonVArrayInfo info = this->common_info(); if (info.type == CommonVArrayInfo::Type::Span) { initialized_copy_n( src.data(), this->size_, const_cast(static_cast(info.data))); } else { const int64_t size = this->size_; for (int64_t i = 0; i < size; i++) { this->set(i, src[i]); } } } /** * Similar to #VArrayImpl::try_assign_GVArray but for mutable virtual arrays. */ virtual bool try_assign_GVMutableArray(GVMutableArray & /*varray*/) const { return false; } }; /** * A virtual array implementation that references that wraps a span. This implementation is used by * mutable and immutable spans to avoid code duplication. */ template class VArrayImpl_For_Span : public VMutableArrayImpl { protected: T *data_ = nullptr; public: VArrayImpl_For_Span(const MutableSpan data) : VMutableArrayImpl(data.size()), data_(data.data()) { } protected: VArrayImpl_For_Span(const int64_t size) : VMutableArrayImpl(size) { } T get(const int64_t index) const final { return data_[index]; } void set(const int64_t index, T value) final { data_[index] = value; } CommonVArrayInfo common_info() const override { return CommonVArrayInfo(CommonVArrayInfo::Type::Span, true, data_); } bool is_same(const VArrayImpl &other) const final { if (other.size() != this->size_) { return false; } const CommonVArrayInfo other_info = other.common_info(); if (other_info.type != CommonVArrayInfo::Type::Span) { return false; } return data_ == static_cast(other_info.data); } void materialize(IndexMask mask, MutableSpan r_span) const override { mask.foreach_index([&](const int64_t i) { r_span[i] = data_[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(data_[i]); }); } void materialize_compressed(IndexMask mask, MutableSpan r_span) const override { BLI_assert(mask.size() == r_span.size()); mask.to_best_mask_type([&](auto best_mask) { for (const int64_t i : IndexRange(best_mask.size())) { r_span[i] = data_[best_mask[i]]; } }); } void materialize_compressed_to_uninitialized(IndexMask mask, MutableSpan r_span) const override { BLI_assert(mask.size() == r_span.size()); T *dst = r_span.data(); mask.to_best_mask_type([&](auto best_mask) { for (const int64_t i : IndexRange(best_mask.size())) { new (dst + i) T(data_[best_mask[i]]); } }); } }; /** * 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; VArrayImpl_For_Span_final(const Span data) /* Cast const away, because the implementation for const and non const spans is shared. */ : VArrayImpl_For_Span({const_cast(data.data()), data.size()}) { } private: CommonVArrayInfo common_info() const final { return CommonVArrayInfo(CommonVArrayInfo::Type::Span, false, this->data_); } }; template inline constexpr bool is_trivial_extended_v> = true; /** * 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 VArrayImpl_For_ArrayContainer : public VArrayImpl_For_Span { private: Container container_; public: VArrayImpl_For_ArrayContainer(Container container) : VArrayImpl_For_Span(int64_t(container.size())), container_(std::move(container)) { this->data_ = const_cast(container_.data()); } }; /** * A virtual array implementation that returns the same value for every index. This class is final * so that it can be devirtualized by the compiler in some cases (e.g. when #devirtualize_varray is * used). */ template class VArrayImpl_For_Single final : public VArrayImpl { private: T value_; public: VArrayImpl_For_Single(T value, const int64_t size) : VArrayImpl(size), value_(std::move(value)) { } protected: T get(const int64_t /*index*/) const override { return value_; } CommonVArrayInfo common_info() const override { return CommonVArrayInfo(CommonVArrayInfo::Type::Single, true, &value_); } void materialize(IndexMask mask, MutableSpan r_span) const override { r_span.fill_indices(mask, value_); } 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(value_); }); } void materialize_compressed(IndexMask mask, MutableSpan r_span) const override { BLI_assert(mask.size() == r_span.size()); UNUSED_VARS_NDEBUG(mask); r_span.fill(value_); } void materialize_compressed_to_uninitialized(IndexMask mask, MutableSpan r_span) const override { BLI_assert(mask.size() == r_span.size()); uninitialized_fill_n(r_span.data(), mask.size(), value_); } }; template inline constexpr bool is_trivial_extended_v> = is_trivial_extended_v; /** * 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)); }); } void materialize_compressed(IndexMask mask, MutableSpan r_span) const override { BLI_assert(mask.size() == r_span.size()); T *dst = r_span.data(); mask.to_best_mask_type([&](auto best_mask) { for (const int64_t i : IndexRange(best_mask.size())) { dst[i] = get_func_(best_mask[i]); } }); } void materialize_compressed_to_uninitialized(IndexMask mask, MutableSpan r_span) const override { BLI_assert(mask.size() == r_span.size()); T *dst = r_span.data(); mask.to_best_mask_type([&](auto best_mask) { for (const int64_t i : IndexRange(best_mask.size())) { new (dst + i) T(get_func_(best_mask[i])); } }); } }; /** * \note This is `final` so that #may_have_ownership can be implemented reliably. */ template class VArrayImpl_For_DerivedSpan final : public VMutableArrayImpl { private: StructT *data_; public: VArrayImpl_For_DerivedSpan(const MutableSpan data) : VMutableArrayImpl(data.size()), data_(data.data()) { } template friend class VArrayImpl_For_DerivedSpan; 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])); }); } void materialize_compressed(IndexMask mask, MutableSpan r_span) const override { BLI_assert(mask.size() == r_span.size()); ElemT *dst = r_span.data(); mask.to_best_mask_type([&](auto best_mask) { for (const int64_t i : IndexRange(best_mask.size())) { dst[i] = GetFunc(data_[best_mask[i]]); } }); } void materialize_compressed_to_uninitialized(IndexMask mask, MutableSpan r_span) const override { BLI_assert(mask.size() == r_span.size()); ElemT *dst = r_span.data(); mask.to_best_mask_type([&](auto best_mask) { for (const int64_t i : IndexRange(best_mask.size())) { new (dst + i) ElemT(GetFunc(data_[best_mask[i]])); } }); } bool is_same(const VArrayImpl &other) const override { if (other.size() != this->size_) { return false; } if (const VArrayImpl_For_DerivedSpan *other_typed = dynamic_cast *>(&other)) { return other_typed->data_ == data_; } if (const VArrayImpl_For_DerivedSpan *other_typed = dynamic_cast *>( &other)) { return other_typed->data_ == data_; } return false; } }; template inline constexpr bool is_trivial_extended_v> = true; 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); template static constexpr VArrayAnyExtraInfo get() { /* These are the only allowed types in the #Any. */ static_assert( std::is_base_of_v, StorageT> || is_same_any_v *, std::shared_ptr>>); /* 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 implementation 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 { if (!storage_.has_value()) { return nullptr; } 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()); } CommonVArrayInfo common_info() const { BLI_assert(*this); return impl_->common_info(); } /** Return true when the virtual array is stored as a span internally. */ bool is_span() const { BLI_assert(*this); const CommonVArrayInfo info = impl_->common_info(); return info.type == CommonVArrayInfo::Type::Span; } /** * Returns the internally used span of the virtual array. This invokes undefined behavior if the * virtual array is not stored as a span internally. */ Span get_internal_span() const { BLI_assert(this->is_span()); const CommonVArrayInfo info = impl_->common_info(); return Span(static_cast(info.data), this->size()); } /** Return true when the virtual array returns the same value for every index. */ bool is_single() const { BLI_assert(*this); const CommonVArrayInfo info = impl_->common_info(); return info.type == CommonVArrayInfo::Type::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()); const CommonVArrayInfo info = impl_->common_info(); return *static_cast(info.data); } /** * Return the value that is returned for every index, if the array is stored as a single value. */ std::optional get_if_single() const { const CommonVArrayInfo info = impl_->common_info(); if (info.type != CommonVArrayInfo::Type::Single) { return std::nullopt; } return *static_cast(info.data); } /** * Return true when the other virtual references the same underlying memory. */ bool is_same(const VArrayCommon &other) const { if (!*this || !other) { return false; } /* Check in both directions in case one does not know how to compare to the other * implementation. */ if (impl_->is_same(*other.impl_)) { return true; } if (other.impl_->is_same(*impl_)) { return true; } return false; } /** 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); } /** Copy some elements of the virtual array into a span. */ void materialize_compressed(IndexMask mask, MutableSpan r_span) const { impl_->materialize_compressed(mask, r_span); } void materialize_compressed_to_uninitialized(IndexMask mask, MutableSpan r_span) const { impl_->materialize_compressed_to_uninitialized(mask, r_span); } /** See #GVArrayImpl::try_assign_GVArray. */ bool try_assign_GVArray(GVArray &varray) const { return impl_->try_assign_GVArray(varray); } }; template class VMutableArray; /** * Various tags to disambiguate constructors of virtual arrays. * Generally it is easier to use `VArray::For*` functions to construct virtual arrays, but * sometimes being able to use the constructor can result in better performance For example, when * constructing the virtual array directly in a vector. Without the constructor one would have to * construct the virtual array first and then move it into the vector. */ namespace varray_tag { struct span { }; struct single_ref { }; struct single { }; } // namespace varray_tag /** * 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)) { } VArray(varray_tag::span /* tag */, Span span) { this->template emplace>(span); } VArray(varray_tag::single /* tag */, T value, const int64_t size) { this->template emplace>(std::move(value), size); } /** * 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(varray_tag::single{}, 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(varray_tag::span{}, 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) { /* Cast const away, because the virtual array implementation for const and non const derived * spans is shared. */ MutableSpan span{const_cast(values.data()), values.size()}; return VArray::For>(span); } /** * 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 CommonVArrayInfo info = this->get_impl()->common_info(); return MutableSpan(const_cast(static_cast(info.data)), this->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(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_; } }; template static constexpr bool is_VArray_v = false; template static constexpr bool is_VArray_v> = true; template static constexpr bool is_VMutableArray_v = false; template static constexpr bool is_VMutableArray_v> = true; /** * 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 * span, this class allocates a new array and copies the values over. * * This should be used in those cases: * - All elements in the virtual array are accessed multiple times. * - In most cases, the underlying virtual array is a span, so no copy is necessary to benefit * from faster access. * - An API is called, that does not accept virtual arrays, but only spans. */ template class VArraySpan final : public Span { private: VArray varray_; Array owned_data_; public: VArraySpan() = default; VArraySpan(VArray varray) : Span(), varray_(std::move(varray)) { if (!varray_) { return; } this->size_ = varray_.size(); const CommonVArrayInfo info = varray_.common_info(); if (info.type == CommonVArrayInfo::Type::Span) { this->data_ = static_cast(info.data); } else { owned_data_.~Array(); new (&owned_data_) Array(varray_.size(), NoInitialization{}); varray_.materialize_to_uninitialized(owned_data_); this->data_ = owned_data_.data(); } } VArraySpan(VArraySpan &&other) : varray_(std::move(other.varray_)), owned_data_(std::move(other.owned_data_)) { if (!varray_) { return; } this->size_ = varray_.size(); const CommonVArrayInfo info = varray_.common_info(); if (info.type == CommonVArrayInfo::Type::Span) { this->data_ = static_cast(info.data); } else { this->data_ = owned_data_.data(); } other.data_ = nullptr; other.size_ = 0; } VArraySpan &operator=(VArraySpan &&other) { if (this == &other) { return *this; } std::destroy_at(this); new (this) VArraySpan(std::move(other)); return *this; } }; /** * Same as #VArraySpan, 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, * if necessary. */ template class MutableVArraySpan final : public MutableSpan { private: VMutableArray varray_; Array owned_data_; bool save_has_been_called_ = false; bool show_not_saved_warning_ = true; public: MutableVArraySpan() = default; /* 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. */ MutableVArraySpan(VMutableArray varray, const bool copy_values_to_span = true) : MutableSpan(), varray_(std::move(varray)) { if (!varray_) { return; } this->size_ = varray_.size(); const CommonVArrayInfo info = varray_.common_info(); if (info.type == CommonVArrayInfo::Type::Span) { this->data_ = const_cast(static_cast(info.data)); } else { if (copy_values_to_span) { owned_data_.~Array(); new (&owned_data_) Array(varray_.size(), NoInitialization{}); varray_.materialize_to_uninitialized(owned_data_); } else { owned_data_.reinitialize(varray_.size()); } this->data_ = owned_data_.data(); } } MutableVArraySpan(MutableVArraySpan &&other) : varray_(std::move(other.varray_)), owned_data_(std::move(other.owned_data_)), show_not_saved_warning_(other.show_not_saved_warning_) { if (!varray_) { return; } this->size_ = varray_.size(); const CommonVArrayInfo info = varray_.common_info(); if (info.type == CommonVArrayInfo::Type::Span) { this->data_ = static_cast(const_cast(info.data)); } else { this->data_ = owned_data_.data(); } other.data_ = nullptr; other.size_ = 0; } ~MutableVArraySpan() { if (varray_) { if (show_not_saved_warning_) { if (!save_has_been_called_) { std::cout << "Warning: Call `save()` to make sure that changes persist in all cases.\n"; } } } } MutableVArraySpan &operator=(MutableVArraySpan &&other) { if (this == &other) { return *this; } std::destroy_at(this); new (this) MutableVArraySpan(std::move(other)); return *this; } const VMutableArray &varray() const { return varray_; } /* Write back all values from a temporary allocated array to the underlying virtual array. */ void save() { save_has_been_called_ = true; if (this->data_ != owned_data_.data()) { return; } varray_.set_all(owned_data_); } void disable_not_applied_warning() { show_not_saved_warning_ = false; } }; template class SingleAsSpan { private: T value_; int64_t size_; public: SingleAsSpan(T value, int64_t size) : value_(std::move(value)), size_(size) { BLI_assert(size_ >= 0); } SingleAsSpan(const VArray &varray) : SingleAsSpan(varray.get_internal_single(), varray.size()) { } const T &operator[](const int64_t index) const { BLI_assert(index >= 0); BLI_assert(index < size_); UNUSED_VARS_NDEBUG(index); return value_; } }; } // namespace blender