/* * 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. */ #ifndef __FN_SPANS_HH__ #define __FN_SPANS_HH__ /** \file * \ingroup fn * * This file implements multiple variants of a span for different use cases. There are two * requirements of the function system that require span implementations other from * blender::Span. * 1. The function system works with a run-time type system (see `CPPType`). Therefore, it has to * deal with types in a generic way. The type of a Span has to be known at compile time. * 2. Span expects an underlying memory buffer that is as large as the span. However, sometimes * we can save some memory and processing when we know that all elements are the same. * * The first requirement is solved with generic spans, which use the "G" prefix. Those * store a CPPType instance to keep track of the type that is currently stored. * * The second requirement is solved with virtual spans. A virtual span behaves like a normal span, * but it might not be backed up by an actual array. Elements in a virtual span are always * immutable. * * Different use cases require different combinations of these properties and therefore use * different data structures. */ #include "BLI_span.hh" #include "FN_cpp_type.hh" namespace blender::fn { /** * A generic span. It behaves just like a blender::Span, but the type is only known at run-time. */ class GSpan { private: const CPPType *type_; const void *buffer_; uint size_; public: GSpan(const CPPType &type, const void *buffer, uint size) : type_(&type), buffer_(buffer), size_(size) { BLI_assert(buffer != nullptr || size == 0); BLI_assert(type.pointer_has_valid_alignment(buffer)); } GSpan(const CPPType &type) : GSpan(type, nullptr, 0) { } template GSpan(Span array) : GSpan(CPPType::get(), (const void *)array.data(), array.size()) { } const CPPType &type() const { return *type_; } bool is_empty() const { return size_ == 0; } uint size() const { return size_; } const void *buffer() const { return buffer_; } const void *operator[](uint index) const { BLI_assert(index < size_); return POINTER_OFFSET(buffer_, type_->size() * index); } template Span typed() const { BLI_assert(type_->is()); return Span((const T *)buffer_, size_); } }; /** * A generic mutable span. It behaves just like a blender::MutableSpan, but the type is only * known at run-time. */ class GMutableSpan { private: const CPPType *type_; void *buffer_; uint size_; public: GMutableSpan(const CPPType &type, void *buffer, uint size) : type_(&type), buffer_(buffer), size_(size) { BLI_assert(buffer != nullptr || size == 0); BLI_assert(type.pointer_has_valid_alignment(buffer)); } GMutableSpan(const CPPType &type) : GMutableSpan(type, nullptr, 0) { } template GMutableSpan(MutableSpan array) : GMutableSpan(CPPType::get(), (void *)array.begin(), array.size()) { } operator GSpan() const { return GSpan(*type_, buffer_, size_); } const CPPType &type() const { return *type_; } bool is_empty() const { return size_ == 0; } uint size() const { return size_; } void *buffer() { return buffer_; } void *operator[](uint index) { BLI_assert(index < size_); return POINTER_OFFSET(buffer_, type_->size() * index); } template MutableSpan typed() { BLI_assert(type_->is()); return MutableSpan((T *)buffer_, size_); } }; enum class VSpanCategory { Single, FullArray, FullPointerArray, }; template struct VSpanBase { protected: uint virtual_size_; VSpanCategory category_; union { struct { const T *data; } single; struct { const T *data; } full_array; struct { const T *const *data; } full_pointer_array; } data_; public: bool is_single_element() const { switch (category_) { case VSpanCategory::Single: return true; case VSpanCategory::FullArray: return virtual_size_ == 1; case VSpanCategory::FullPointerArray: return virtual_size_ == 1; } BLI_assert(false); return false; } bool is_empty() const { return this->virtual_size_ == 0; } uint size() const { return this->virtual_size_; } }; BLI_STATIC_ASSERT((sizeof(VSpanBase) == sizeof(VSpanBase>)), "should not depend on the size of the type"); /** * A virtual span. It behaves like a blender::Span, but might not be backed up by an actual * array. */ template class VSpan : public VSpanBase { friend class GVSpan; VSpan(const VSpanBase &values) { memcpy(this, &values, sizeof(VSpanBase)); } public: VSpan() { this->virtual_size_ = 0; this->category_ = VSpanCategory::FullArray; this->data_.full_array.data = nullptr; } VSpan(Span values) { this->virtual_size_ = values.size(); this->category_ = VSpanCategory::FullArray; this->data_.full_array.data = values.begin(); } VSpan(MutableSpan values) : VSpan(Span(values)) { } VSpan(Span values) { this->virtual_size_ = values.size(); this->category_ = VSpanCategory::FullPointerArray; this->data_.full_pointer_array.data = values.begin(); } static VSpan FromSingle(const T *value, uint virtual_size) { VSpan ref; ref.virtual_size_ = virtual_size; ref.category_ = VSpanCategory::Single; ref.data_.single.data = value; return ref; } const T &operator[](uint index) const { BLI_assert(index < this->virtual_size_); switch (this->category_) { case VSpanCategory::Single: return *this->data_.single.data; case VSpanCategory::FullArray: return this->data_.full_array.data[index]; case VSpanCategory::FullPointerArray: return *this->data_.full_pointer_array.data[index]; } BLI_assert(false); return *this->data_.single.data; } }; /** * A generic virtual span. It behaves like a blender::Span, but the type is only known at * run-time and it might not be backed up by an actual array. */ class GVSpan : public VSpanBase { private: const CPPType *type_; GVSpan() = default; public: GVSpan(const CPPType &type) { this->type_ = &type; this->virtual_size_ = 0; this->category_ = VSpanCategory::FullArray; this->data_.full_array.data = nullptr; } GVSpan(GSpan values) { this->type_ = &values.type(); this->virtual_size_ = values.size(); this->category_ = VSpanCategory::FullArray; this->data_.full_array.data = values.buffer(); } GVSpan(GMutableSpan values) : GVSpan(GSpan(values)) { } template GVSpan(const VSpanBase &values) { this->type_ = &CPPType::get(); memcpy(this, &values, sizeof(VSpanBase)); } template GVSpan(Span values) : GVSpan(GSpan(values)) { } template GVSpan(MutableSpan values) : GVSpan(GSpan(values)) { } static GVSpan FromSingle(const CPPType &type, const void *value, uint virtual_size) { GVSpan ref; ref.type_ = &type; ref.virtual_size_ = virtual_size; ref.category_ = VSpanCategory::Single; ref.data_.single.data = value; return ref; } static GVSpan FromSingleWithMaxSize(const CPPType &type, const void *value) { return GVSpan::FromSingle(type, value, UINT32_MAX); } static GVSpan FromDefault(const CPPType &type) { return GVSpan::FromSingleWithMaxSize(type, type.default_value()); } static GVSpan FromFullPointerArray(const CPPType &type, const void *const *values, uint size) { GVSpan ref; ref.type_ = &type; ref.virtual_size_ = size; ref.category_ = VSpanCategory::FullPointerArray; ref.data_.full_pointer_array.data = values; return ref; } const CPPType &type() const { return *this->type_; } const void *operator[](uint index) const { BLI_assert(index < this->virtual_size_); switch (this->category_) { case VSpanCategory::Single: return this->data_.single.data; case VSpanCategory::FullArray: return POINTER_OFFSET(this->data_.full_array.data, index * type_->size()); case VSpanCategory::FullPointerArray: return this->data_.full_pointer_array.data[index]; } BLI_assert(false); return this->data_.single.data; } template VSpan typed() const { BLI_assert(type_->is()); return VSpan(*this); } const void *as_single_element() const { BLI_assert(this->is_single_element()); return (*this)[0]; } void materialize_to_uninitialized(void *dst) const { this->materialize_to_uninitialized(IndexRange(virtual_size_), dst); } void materialize_to_uninitialized(IndexMask mask, void *dst) const { BLI_assert(this->size() >= mask.min_array_size()); uint element_size = type_->size(); for (uint i : mask) { type_->copy_to_uninitialized((*this)[i], POINTER_OFFSET(dst, element_size * i)); } } }; } // namespace blender::fn #endif /* __FN_SPANS_HH__ */