/* 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 CommonVArrayInfo common_info() const; virtual void materialize(const IndexMask mask, void *dst) const; virtual void materialize_to_uninitialized(const IndexMask mask, void *dst) const; virtual void materialize_compressed(IndexMask mask, void *dst) const; virtual void materialize_compressed_to_uninitialized(IndexMask mask, void *dst) const; virtual bool try_assign_VArray(void *varray) const; }; /* A generic version of #VMutableArrayImpl. */ class GVMutableArrayImpl : public GVArrayImpl { public: GVMutableArrayImpl(const CPPType &type, int64_t size) : GVArrayImpl(type, 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 * /*buffer*/) -> const GVArrayImpl * { return nullptr; }; template static constexpr 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; const GVArrayImpl *impl_ = nullptr; Storage storage_; protected: GVArrayCommon() = default; GVArrayCommon(const GVArrayCommon &other); GVArrayCommon(GVArrayCommon &&other) noexcept; GVArrayCommon(const GVArrayImpl *impl); GVArrayCommon(std::shared_ptr impl); ~GVArrayCommon(); template 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 bool try_assign_VArray(VArray &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; void materialize_compressed(IndexMask mask, void *dst) const; void materialize_compressed_to_uninitialized(IndexMask mask, void *dst) const; CommonVArrayInfo common_info() 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 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 impl); GVArray(varray_tag::span /* tag */, GSpan span); GVArray(varray_tag::single_ref /* tag */, const CPPType &type, int64_t size, const void *value); GVArray(varray_tag::single /* tag */, const CPPType &type, int64_t size, const void *value); template GVArray(const VArray &varray); template VArray typed() const; template 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 impl); template GVMutableArray(const VMutableArray &varray); template VMutableArray typed() const; template 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 bool try_assign_VMutableArray(VMutableArray &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 #GVArraySpan and #GMutableVArraySpan. * \{ */ /* A generic version of VArraySpan. */ class GVArraySpan : public GSpan { private: GVArray varray_; void *owned_data_ = nullptr; public: GVArraySpan(); GVArraySpan(GVArray varray); GVArraySpan(GVArraySpan &&other); ~GVArraySpan(); GVArraySpan &operator=(GVArraySpan &&other); }; /* A generic version of MutableVArraySpan. */ class GMutableVArraySpan : public GMutableSpan, NonCopyable, NonMovable { private: GVMutableArray varray_; void *owned_data_ = nullptr; bool save_has_been_called_ = false; bool show_not_saved_warning_ = true; public: GMutableVArraySpan(); GMutableVArraySpan(GVMutableArray varray, bool copy_values_to_span = true); GMutableVArraySpan(GMutableVArraySpan &&other); ~GMutableVArraySpan(); GMutableVArraySpan &operator=(GMutableVArraySpan &&other); const GVMutableArray &varray() const; 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 class GVArrayImpl_For_VArray : public GVArrayImpl { protected: VArray varray_; public: GVArrayImpl_For_VArray(VArray varray) : GVArrayImpl(CPPType::get(), varray.size()), varray_(std::move(varray)) { } protected: void get(const int64_t index, void *r_value) const override { *static_cast(r_value) = varray_[index]; } void get_to_uninitialized(const int64_t index, void *r_value) const override { new (r_value) T(varray_[index]); } void materialize(const IndexMask mask, void *dst) const override { varray_.materialize(mask, MutableSpan(static_cast(dst), mask.min_array_size())); } void materialize_to_uninitialized(const IndexMask mask, void *dst) const override { varray_.materialize_to_uninitialized( mask, MutableSpan(static_cast(dst), mask.min_array_size())); } void materialize_compressed(const IndexMask mask, void *dst) const override { varray_.materialize_compressed(mask, MutableSpan(static_cast(dst), mask.size())); } void materialize_compressed_to_uninitialized(const IndexMask mask, void *dst) const override { varray_.materialize_compressed_to_uninitialized( mask, MutableSpan(static_cast(dst), mask.size())); } bool try_assign_VArray(void *varray) const override { *(VArray *)varray = varray_; return true; } CommonVArrayInfo common_info() const override { return varray_.common_info(); } }; /* Used to convert any generic virtual array into a typed one. */ template class VArrayImpl_For_GVArray : public VArrayImpl { protected: GVArray varray_; public: VArrayImpl_For_GVArray(GVArray varray) : VArrayImpl(varray.size()), varray_(std::move(varray)) { BLI_assert(varray_); BLI_assert(varray_.type().template is()); } protected: T get(const int64_t index) const override { T value; varray_.get(index, &value); return value; } CommonVArrayInfo common_info() const override { return varray_.common_info(); } bool try_assign_GVArray(GVArray &varray) const override { varray = varray_; return true; } void materialize(IndexMask mask, MutableSpan r_span) const override { varray_.materialize(mask, r_span.data()); } void materialize_to_uninitialized(IndexMask mask, MutableSpan r_span) const override { varray_.materialize_to_uninitialized(mask, r_span.data()); } void materialize_compressed(IndexMask mask, MutableSpan r_span) const override { varray_.materialize_compressed(mask, r_span.data()); } void materialize_compressed_to_uninitialized(IndexMask mask, MutableSpan r_span) const override { varray_.materialize_compressed_to_uninitialized(mask, r_span.data()); } }; /* Used to convert any typed virtual mutable array into a generic one. */ template class GVMutableArrayImpl_For_VMutableArray : public GVMutableArrayImpl { protected: VMutableArray varray_; public: GVMutableArrayImpl_For_VMutableArray(VMutableArray varray) : GVMutableArrayImpl(CPPType::get(), varray.size()), varray_(std::move(varray)) { } protected: void get(const int64_t index, void *r_value) const override { *static_cast(r_value) = varray_[index]; } void get_to_uninitialized(const int64_t index, void *r_value) const override { new (r_value) T(varray_[index]); } CommonVArrayInfo common_info() const override { return varray_.common_info(); } 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_ = *static_cast(value); varray_.set(index, std::move(value_)); value_.~T(); } void set_by_move(const int64_t index, void *value) override { T &value_ = *static_cast(value); varray_.set(index, std::move(value_)); } void set_all(const void *src) override { varray_.set_all(Span(static_cast(src), size_)); } void materialize(const IndexMask mask, void *dst) const override { varray_.materialize(mask, MutableSpan(static_cast(dst), mask.min_array_size())); } void materialize_to_uninitialized(const IndexMask mask, void *dst) const override { varray_.materialize_to_uninitialized( mask, MutableSpan(static_cast(dst), mask.min_array_size())); } void materialize_compressed(const IndexMask mask, void *dst) const override { varray_.materialize_compressed(mask, MutableSpan(static_cast(dst), mask.size())); } void materialize_compressed_to_uninitialized(const IndexMask mask, void *dst) const override { varray_.materialize_compressed_to_uninitialized( mask, MutableSpan(static_cast(dst), mask.size())); } bool try_assign_VArray(void *varray) const override { *(VArray *)varray = varray_; return true; } bool try_assign_VMutableArray(void *varray) const override { *(VMutableArray *)varray = varray_; return true; } }; /* Used to convert an generic mutable virtual array into a typed one. */ template class VMutableArrayImpl_For_GVMutableArray : public VMutableArrayImpl { protected: GVMutableArray varray_; public: VMutableArrayImpl_For_GVMutableArray(GVMutableArray varray) : VMutableArrayImpl(varray.size()), varray_(varray) { BLI_assert(varray_); BLI_assert(varray_.type().template is()); } 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); } CommonVArrayInfo common_info() const override { return varray_.common_info(); } bool try_assign_GVArray(GVArray &varray) const override { varray = varray_; return true; } bool try_assign_GVMutableArray(GVMutableArray &varray) const override { varray = varray_; return true; } void materialize(IndexMask mask, MutableSpan r_span) const override { varray_.materialize(mask, r_span.data()); } void materialize_to_uninitialized(IndexMask mask, MutableSpan r_span) const override { varray_.materialize_to_uninitialized(mask, r_span.data()); } void materialize_compressed(IndexMask mask, MutableSpan r_span) const override { varray_.materialize_compressed(mask, r_span.data()); } void materialize_compressed_to_uninitialized(IndexMask mask, MutableSpan r_span) const override { varray_.materialize_compressed_to_uninitialized(mask, r_span.data()); } }; /** \} */ /* -------------------------------------------------------------------- */ /** \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) : GVMutableArrayImpl(span.type(), span.size()), data_(span.data()), element_size_(span.type().size()) { } protected: GVArrayImpl_For_GSpan(const CPPType &type, int64_t size) : GVMutableArrayImpl(type, size), element_size_(type.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; CommonVArrayInfo common_info() const override; virtual void materialize(const IndexMask mask, void *dst) const override; virtual void materialize_to_uninitialized(const IndexMask mask, void *dst) const override; virtual void materialize_compressed(const IndexMask mask, void *dst) const override; virtual void materialize_compressed_to_uninitialized(const IndexMask mask, void *dst) const override; }; class GVArrayImpl_For_GSpan_final final : public GVArrayImpl_For_GSpan { public: using GVArrayImpl_For_GSpan::GVArrayImpl_For_GSpan; private: CommonVArrayInfo common_info() const override; }; template<> inline constexpr bool is_trivial_extended_v = true; /** \} */ /* -------------------------------------------------------------------- */ /** \name #GVArrayImpl_For_SingleValueRef. * \{ */ 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 index, void *r_value) const override; void get_to_uninitialized(const int64_t index, void *r_value) const override; CommonVArrayInfo common_info() const override; void materialize(const IndexMask mask, void *dst) const override; void materialize_to_uninitialized(const IndexMask mask, void *dst) const override; void materialize_compressed(const IndexMask mask, void *dst) const override; void materialize_compressed_to_uninitialized(const IndexMask mask, void *dst) const override; }; class GVArrayImpl_For_SingleValueRef_final final : public GVArrayImpl_For_SingleValueRef { public: using GVArrayImpl_For_SingleValueRef::GVArrayImpl_For_SingleValueRef; private: CommonVArrayInfo common_info() const override; }; template<> inline constexpr bool is_trivial_extended_v = true; /** \} */ /* -------------------------------------------------------------------- */ /** \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 inline bool GVMutableArray::try_assign_VMutableArray(VMutableArray &varray) const { BLI_assert(impl_->type().is()); return this->get_impl()->try_assign_VMutableArray(&varray); } inline GVMutableArrayImpl *GVMutableArray::get_impl() const { return const_cast(static_cast(impl_)); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Inline methods for #GVArrayCommon. * \{ */ template inline void GVArrayCommon::emplace(Args &&...args) { static_assert(std::is_base_of_v); if constexpr (std::is_copy_constructible_v && Storage::template is_inline_v) { impl_ = &storage_.template emplace(std::forward(args)...); } else { std::shared_ptr ptr = std::make_shared(std::forward(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 inline T GVArrayCommon::get(const int64_t index) const { BLI_assert(index >= 0); BLI_assert(index < this->size()); BLI_assert(this->type().is()); 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 inline bool GVArrayCommon::try_assign_VArray(VArray &varray) const { BLI_assert(impl_->type().is()); return impl_->try_assign_VArray(&varray); } inline const CPPType &GVArrayCommon::type() const { return impl_->type(); } inline GVArrayCommon::operator bool() const { return impl_ != nullptr; } inline CommonVArrayInfo GVArrayCommon::common_info() const { return impl_->common_info(); } 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. * \{ */ inline GVArray::GVArray(varray_tag::span /* tag */, const 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(span.data()), span.size()}; this->emplace(mutable_span); } inline GVArray::GVArray(varray_tag::single_ref /* tag */, const CPPType &type, const int64_t size, const void *value) { this->emplace(type, size, value); } namespace detail { template constexpr GVArrayAnyExtraInfo GVArrayAnyExtraInfo::get() { static_assert(std::is_base_of_v || is_same_any_v>); if constexpr (std::is_base_of_v) { 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 template inline GVArray GVArray::For(Args &&...args) { static_assert(std::is_base_of_v); GVArray varray; varray.template emplace(std::forward(args)...); return varray; } template inline GVArray::GVArray(const VArray &varray) { if (!varray) { return; } const CommonVArrayInfo info = varray.common_info(); if (info.type == CommonVArrayInfo::Type::Single) { *this = GVArray::ForSingle(CPPType::get(), varray.size(), info.data); return; } /* Need to check for ownership, because otherwise the referenced data can be destructed when * #this is destructed. */ if (info.type == CommonVArrayInfo::Type::Span && !info.may_have_ownership) { *this = GVArray::ForSpan(GSpan(CPPType::get(), info.data, varray.size())); return; } if (varray.try_assign_GVArray(*this)) { return; } *this = GVArray::For>(varray); } template inline VArray GVArray::typed() const { if (!*this) { return {}; } BLI_assert(impl_->type().is()); const CommonVArrayInfo info = this->common_info(); if (info.type == CommonVArrayInfo::Type::Single) { return VArray::ForSingle(*static_cast(info.data), this->size()); } /* Need to check for ownership, because otherwise the referenced data can be destructed when * #this is destructed. */ if (info.type == CommonVArrayInfo::Type::Span && !info.may_have_ownership) { return VArray::ForSpan(Span(static_cast(info.data), this->size())); } VArray varray; if (this->try_assign_VArray(varray)) { return varray; } return VArray::template For>(*this); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Inline methods for #GVMutableArray. * \{ */ template inline GVMutableArray GVMutableArray::For(Args &&...args) { static_assert(std::is_base_of_v); GVMutableArray varray; varray.emplace(std::forward(args)...); return varray; } template inline GVMutableArray::GVMutableArray(const VMutableArray &varray) { if (!varray) { return; } const CommonVArrayInfo info = varray.common_info(); if (info.type == CommonVArrayInfo::Type::Span && !info.may_have_ownership) { *this = GVMutableArray::ForSpan( GMutableSpan(CPPType::get(), const_cast(info.data), varray.size())); return; } if (varray.try_assign_GVMutableArray(*this)) { return; } *this = GVMutableArray::For>(varray); } template inline VMutableArray GVMutableArray::typed() const { if (!*this) { return {}; } BLI_assert(this->type().is()); const CommonVArrayInfo info = this->common_info(); if (info.type == CommonVArrayInfo::Type::Span && !info.may_have_ownership) { return VMutableArray::ForSpan( MutableSpan(const_cast(static_cast(info.data)), this->size())); } VMutableArray varray; if (this->try_assign_VMutableArray(varray)) { return varray; } return VMutableArray::template For>(*this); } /** \} */ } // namespace blender