diff options
19 files changed, 2246 insertions, 41 deletions
diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt index 9ce1d3ac2fe..dce7eb71376 100644 --- a/source/blender/functions/CMakeLists.txt +++ b/source/blender/functions/CMakeLists.txt @@ -29,8 +29,16 @@ set(INC_SYS set(SRC intern/cpp_types.cc + FN_array_spans.hh FN_cpp_type.hh FN_cpp_types.hh + FN_multi_function.hh + FN_multi_function_context.hh + FN_multi_function_data_type.hh + FN_multi_function_param_type.hh + FN_multi_function_params.hh + FN_multi_function_signature.hh + FN_spans.hh ) set(LIB diff --git a/source/blender/functions/FN_array_spans.hh b/source/blender/functions/FN_array_spans.hh new file mode 100644 index 00000000000..ca818ad6b11 --- /dev/null +++ b/source/blender/functions/FN_array_spans.hh @@ -0,0 +1,220 @@ +/* + * 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_ARRAY_SPANS_HH__ +#define __FN_ARRAY_SPANS_HH__ + +/** \file + * \ingroup fn + * + * An ArraySpan is a span where every element contains an array (instead of a single element as is + * the case in a normal span). It's main use case is to reference many small arrays. + */ + +#include "FN_spans.hh" + +namespace blender { +namespace fn { + +/** + * A virtual array span. Every element of this span contains a virtual span. So it behaves like a + * blender::Span, but might not be backed up by an actual array. + */ +template<typename T> class VArraySpan { + private: + /** + * Depending on the use case, the referenced data might have a different structure. More + * categories can be added when necessary. + */ + enum Category { + SingleArray, + StartsAndSizes, + }; + + uint m_virtual_size; + Category m_category; + + union { + struct { + const T *start; + uint size; + } single_array; + struct { + const T *const *starts; + const uint *sizes; + } starts_and_sizes; + } m_data; + + public: + VArraySpan() + { + m_virtual_size = 0; + m_category = StartsAndSizes; + m_data.starts_and_sizes.starts = nullptr; + m_data.starts_and_sizes.sizes = nullptr; + } + + VArraySpan(Span<T> span, uint virtual_size) + { + m_virtual_size = virtual_size; + m_category = SingleArray; + m_data.single_array.start = span.data(); + m_data.single_array.size = span.size(); + } + + VArraySpan(Span<const T *> starts, Span<uint> sizes) + { + BLI_assert(starts.size() == sizes.size()); + m_virtual_size = starts.size(); + m_category = StartsAndSizes; + m_data.starts_and_sizes.starts = starts.begin(); + m_data.starts_and_sizes.sizes = sizes.begin(); + } + + bool is_empty() const + { + return m_virtual_size == 0; + } + + uint size() const + { + return m_virtual_size; + } + + VSpan<T> operator[](uint index) const + { + BLI_assert(index < m_virtual_size); + switch (m_category) { + case SingleArray: + return VSpan<T>(Span<T>(m_data.single_array.start, m_data.single_array.size)); + case StartsAndSizes: + return VSpan<T>( + Span<T>(m_data.starts_and_sizes.starts[index], m_data.starts_and_sizes.sizes[index])); + } + BLI_assert(false); + return {}; + } +}; + +/** + * A generic virtual array span. It's just like a VArraySpan, but the type is only known at + * run-time. + */ +class GVArraySpan { + private: + /** + * Depending on the use case, the referenced data might have a different structure. More + * categories can be added when necessary. + */ + enum Category { + SingleArray, + StartsAndSizes, + }; + + const CPPType *m_type; + uint m_virtual_size; + Category m_category; + + union { + struct { + const void *values; + uint size; + } single_array; + struct { + const void *const *starts; + const uint *sizes; + } starts_and_sizes; + } m_data; + + GVArraySpan() = default; + + public: + GVArraySpan(const CPPType &type) + { + m_type = &type; + m_virtual_size = 0; + m_category = StartsAndSizes; + m_data.starts_and_sizes.starts = nullptr; + m_data.starts_and_sizes.sizes = nullptr; + } + + GVArraySpan(GSpan array, uint virtual_size) + { + m_type = &array.type(); + m_virtual_size = virtual_size; + m_category = SingleArray; + m_data.single_array.values = array.buffer(); + m_data.single_array.size = array.size(); + } + + GVArraySpan(const CPPType &type, Span<const void *> starts, Span<uint> sizes) + { + BLI_assert(starts.size() == sizes.size()); + m_type = &type; + m_virtual_size = starts.size(); + m_category = StartsAndSizes; + m_data.starts_and_sizes.starts = starts.begin(); + m_data.starts_and_sizes.sizes = sizes.begin(); + } + + bool is_empty() const + { + return m_virtual_size == 0; + } + + uint size() const + { + return m_virtual_size; + } + + const CPPType &type() const + { + return *m_type; + } + + template<typename T> VArraySpan<T> typed() const + { + BLI_assert(CPPType::get<T>() == *m_type); + switch (m_category) { + case SingleArray: + return VArraySpan<T>( + Span<T>((const T *)m_data.single_array.values, m_data.single_array.size)); + case StartsAndSizes: + return VArraySpan<T>( + Span<const T *>((const T *const *)m_data.starts_and_sizes.starts, m_virtual_size), + Span<uint>(m_data.starts_and_sizes.sizes, m_virtual_size)); + } + } + + GVSpan operator[](uint index) const + { + BLI_assert(index < m_virtual_size); + switch (m_category) { + case SingleArray: + return GVSpan(GSpan(*m_type, m_data.single_array.values, m_data.single_array.size)); + case StartsAndSizes: + return GVSpan(GSpan( + *m_type, m_data.starts_and_sizes.starts[index], m_data.starts_and_sizes.sizes[index])); + } + BLI_assert(false); + return GVSpan(*m_type); + } +}; + +} // namespace fn +} // namespace blender + +#endif /* __FN_ARRAY_SPANS_HH__ */ diff --git a/source/blender/functions/FN_cpp_type.hh b/source/blender/functions/FN_cpp_type.hh index 1dc72e16e77..df5218ed5ba 100644 --- a/source/blender/functions/FN_cpp_type.hh +++ b/source/blender/functions/FN_cpp_type.hh @@ -71,11 +71,7 @@ #include "BLI_string_ref.hh" namespace blender { -namespace FN { - -using blender::IndexMask; -using blender::StringRef; -using blender::StringRefNull; +namespace fn { class CPPType { public: @@ -241,14 +237,14 @@ class CPPType { void construct_default_n(void *ptr, uint n) const { - BLI_assert(this->pointer_can_point_to_instance(ptr)); + BLI_assert(this->pointer_has_valid_alignment(ptr)); m_construct_default_n(ptr, n); } void construct_default_indices(void *ptr, IndexMask index_mask) const { - BLI_assert(this->pointer_can_point_to_instance(ptr)); + BLI_assert(this->pointer_has_valid_alignment(ptr)); m_construct_default_indices(ptr, index_mask); } @@ -270,14 +266,14 @@ class CPPType { void destruct_n(void *ptr, uint n) const { - BLI_assert(this->pointer_can_point_to_instance(ptr)); + BLI_assert(this->pointer_has_valid_alignment(ptr)); m_destruct_n(ptr, n); } void destruct_indices(void *ptr, IndexMask index_mask) const { - BLI_assert(this->pointer_can_point_to_instance(ptr)); + BLI_assert(this->pointer_has_valid_alignment(ptr)); m_destruct_indices(ptr, index_mask); } @@ -300,8 +296,8 @@ class CPPType { void copy_to_initialized_n(const void *src, void *dst, uint n) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_copy_to_initialized_n(src, dst, n); } @@ -309,8 +305,8 @@ class CPPType { void copy_to_initialized_indices(const void *src, void *dst, IndexMask index_mask) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_copy_to_initialized_indices(src, dst, index_mask); } @@ -335,8 +331,8 @@ class CPPType { void copy_to_uninitialized_n(const void *src, void *dst, uint n) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_copy_to_uninitialized_n(src, dst, n); } @@ -344,8 +340,8 @@ class CPPType { void copy_to_uninitialized_indices(const void *src, void *dst, IndexMask index_mask) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_copy_to_uninitialized_indices(src, dst, index_mask); } @@ -370,8 +366,8 @@ class CPPType { void relocate_to_initialized_n(void *src, void *dst, uint n) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_relocate_to_initialized_n(src, dst, n); } @@ -379,8 +375,8 @@ class CPPType { void relocate_to_initialized_indices(void *src, void *dst, IndexMask index_mask) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_relocate_to_initialized_indices(src, dst, index_mask); } @@ -405,8 +401,8 @@ class CPPType { void relocate_to_uninitialized_n(void *src, void *dst, uint n) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_relocate_to_uninitialized_n(src, dst, n); } @@ -414,8 +410,8 @@ class CPPType { void relocate_to_uninitialized_indices(void *src, void *dst, IndexMask index_mask) const { BLI_assert(src != dst); - BLI_assert(this->pointer_can_point_to_instance(src)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(src)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_relocate_to_uninitialized_indices(src, dst, index_mask); } @@ -435,8 +431,8 @@ class CPPType { void fill_initialized_indices(const void *value, void *dst, IndexMask index_mask) const { - BLI_assert(this->pointer_can_point_to_instance(value)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(value)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_fill_initialized_indices(value, dst, index_mask); } @@ -456,8 +452,8 @@ class CPPType { void fill_uninitialized_indices(const void *value, void *dst, IndexMask index_mask) const { - BLI_assert(this->pointer_can_point_to_instance(value)); - BLI_assert(this->pointer_can_point_to_instance(dst)); + BLI_assert(this->pointer_has_valid_alignment(value)); + BLI_assert(this->pointer_has_valid_alignment(dst)); m_fill_uninitialized_indices(value, dst, index_mask); } @@ -719,15 +715,15 @@ static std::unique_ptr<const CPPType> create_cpp_type(StringRef name, const T &d return std::unique_ptr<const CPPType>(type); } -} // namespace FN +} // namespace fn } // namespace blender #define MAKE_CPP_TYPE(IDENTIFIER, TYPE_NAME) \ static TYPE_NAME default_value_##IDENTIFIER; \ - static std::unique_ptr<const blender::FN::CPPType> CPPTYPE_##IDENTIFIER##_owner = \ - blender::FN::create_cpp_type<TYPE_NAME>(STRINGIFY(IDENTIFIER), default_value_##IDENTIFIER); \ - const blender::FN::CPPType &CPPType_##IDENTIFIER = *CPPTYPE_##IDENTIFIER##_owner; \ - template<> const blender::FN::CPPType &blender::FN::CPPType::get<TYPE_NAME>() \ + static std::unique_ptr<const blender::fn::CPPType> CPPTYPE_##IDENTIFIER##_owner = \ + blender::fn::create_cpp_type<TYPE_NAME>(STRINGIFY(IDENTIFIER), default_value_##IDENTIFIER); \ + const blender::fn::CPPType &CPPType_##IDENTIFIER = *CPPTYPE_##IDENTIFIER##_owner; \ + template<> const blender::fn::CPPType &blender::fn::CPPType::get<TYPE_NAME>() \ { \ return CPPType_##IDENTIFIER; \ } diff --git a/source/blender/functions/FN_cpp_types.hh b/source/blender/functions/FN_cpp_types.hh index 6ee8788cd52..c39b284d5c4 100644 --- a/source/blender/functions/FN_cpp_types.hh +++ b/source/blender/functions/FN_cpp_types.hh @@ -27,7 +27,7 @@ #include "FN_cpp_type.hh" namespace blender { -namespace FN { +namespace fn { extern const CPPType &CPPType_bool; @@ -44,7 +44,7 @@ extern const CPPType &CPPType_Color4b; extern const CPPType &CPPType_string; -} // namespace FN +} // namespace fn } // namespace blender #endif /* __FN_CPP_TYPES_HH__ */ diff --git a/source/blender/functions/FN_generic_vector_array.hh b/source/blender/functions/FN_generic_vector_array.hh new file mode 100644 index 00000000000..90c0fbad136 --- /dev/null +++ b/source/blender/functions/FN_generic_vector_array.hh @@ -0,0 +1,181 @@ +/* + * 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_GENERIC_VECTOR_ARRAY_HH__ +#define __FN_GENERIC_VECTOR_ARRAY_HH__ + +/** \file + * \ingroup fn + * + * A `GVectorArray` is a container for a fixed amount of dynamically growing arrays with a generic + * type. Its main use case is to store many small vectors with few separate allocations. Using this + * structure is generally more efficient than allocating each small vector separately. + * + * `GVectorArrayRef<T>` is a typed reference to a GVectorArray and makes it easier and safer to + * work with the class when the type is known at compile time. + */ + +#include "FN_array_spans.hh" +#include "FN_cpp_type.hh" + +#include "BLI_array.hh" +#include "BLI_linear_allocator.hh" +#include "BLI_utility_mixins.hh" + +namespace blender { +namespace fn { + +template<typename T> class GVectorArrayRef; + +class GVectorArray : NonCopyable, NonMovable { + private: + const CPPType &m_type; + uint m_element_size; + Array<void *, 1> m_starts; + Array<uint, 1> m_lengths; + Array<uint, 1> m_capacities; + LinearAllocator<> m_allocator; + + template<typename T> friend class GVectorArrayRef; + + public: + GVectorArray() = delete; + + GVectorArray(const CPPType &type, uint array_size) + : m_type(type), + m_element_size(type.size()), + m_starts(array_size), + m_lengths(array_size), + m_capacities(array_size) + { + m_starts.fill(nullptr); + m_lengths.fill(0); + m_capacities.fill(0); + } + + ~GVectorArray() + { + if (m_type.is_trivially_destructible()) { + return; + } + + for (uint i : m_starts.index_range()) { + m_type.destruct_n(m_starts[i], m_lengths[i]); + } + } + + operator GVArraySpan() const + { + return GVArraySpan(m_type, m_starts.as_span(), m_lengths); + } + + bool is_empty() const + { + return m_starts.size() == 0; + } + + uint size() const + { + return m_starts.size(); + } + + const CPPType &type() const + { + return m_type; + } + + Span<const void *> starts() const + { + return m_starts.as_span(); + } + + Span<uint> lengths() const + { + return m_lengths; + } + + void append(uint index, const void *src) + { + uint old_length = m_lengths[index]; + if (old_length == m_capacities[index]) { + this->grow_at_least_one(index); + } + + void *dst = POINTER_OFFSET(m_starts[index], m_element_size * old_length); + m_type.copy_to_uninitialized(src, dst); + m_lengths[index]++; + } + + GMutableSpan operator[](uint index) + { + BLI_assert(index < m_starts.size()); + return GMutableSpan(m_type, m_starts[index], m_lengths[index]); + } + template<typename T> GVectorArrayRef<T> typed() + { + return GVectorArrayRef<T>(*this); + } + + private: + void grow_at_least_one(uint index) + { + BLI_assert(m_lengths[index] == m_capacities[index]); + uint new_capacity = m_lengths[index] * 2 + 1; + + void *new_buffer = m_allocator.allocate(m_element_size * new_capacity, m_type.alignment()); + m_type.relocate_to_uninitialized_n(m_starts[index], new_buffer, m_lengths[index]); + + m_starts[index] = new_buffer; + m_capacities[index] = new_capacity; + } +}; + +template<typename T> class GVectorArrayRef { + private: + GVectorArray *m_vector_array; + + public: + GVectorArrayRef(GVectorArray &vector_array) : m_vector_array(&vector_array) + { + BLI_assert(vector_array.m_type == CPPType::get<T>()); + } + + void append(uint index, const T &value) + { + m_vector_array->append(index, &value); + } + + MutableSpan<T> operator[](uint index) + { + BLI_assert(index < m_vector_array->m_starts.size()); + return MutableSpan<T>((T *)m_vector_array->m_starts[index], m_vector_array->m_lengths[index]); + } + + uint size() const + { + return m_vector_array->size(); + } + + bool is_empty() const + { + return m_vector_array->is_empty(); + } +}; + +} // namespace fn +} // namespace blender + +#endif /* __FN_GENERIC_VECTOR_ARRAY_HH__ */ diff --git a/source/blender/functions/FN_multi_function.hh b/source/blender/functions/FN_multi_function.hh new file mode 100644 index 00000000000..3c9d833459a --- /dev/null +++ b/source/blender/functions/FN_multi_function.hh @@ -0,0 +1,106 @@ +/* + * 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_MULTI_FUNCTION_HH__ +#define __FN_MULTI_FUNCTION_HH__ + +/** \file + * \ingroup fn + * + * A `MultiFunction` encapsulates a function that is optimized for throughput (instead of latency). + * The throughput is optimized by always processing many elements at once, instead of each element + * separately. This is ideal for functions that are evaluated often (e.g. for every particle). + * + * By processing a lot of data at once, individual functions become easier to optimize for humans + * and for the compiler. Furthermore, performance profiles become easier to understand and show + * better where bottlenecks are. + * + * Every multi-function has a name and an ordered list of parameters. Parameters are used for input + * and output. In fact, there are three kinds of parameters: inputs, outputs and mutable (which is + * combination of input and output). + * + * To call a multi-function, one has to provide three things: + * - `MFParams`: This references the input and output arrays that the function works with. The + * arrays are not owned by MFParams. + * - `IndexMask`: An array of indices indicating which indices in the provided arrays should be + * touched/processed. + * - `MFContext`: Further information for the called function. + * + * A new multi-function is generally implemented as follows: + * 1. Create a new subclass of MultiFunction. + * 2. Implement a constructor that initialized the signature of the function. + * 3. Override the `call` function. + */ + +#include "FN_multi_function_context.hh" +#include "FN_multi_function_params.hh" + +namespace blender { +namespace fn { + +class MultiFunction { + private: + MFSignature m_signature; + + public: + virtual ~MultiFunction() + { + } + + virtual void call(IndexMask mask, MFParams params, MFContext context) const = 0; + + IndexRange param_indices() const + { + return m_signature.param_types.index_range(); + } + + MFParamType param_type(uint param_index) const + { + return m_signature.param_types[param_index]; + } + + StringRefNull param_name(uint param_index) const + { + return m_signature.param_names[param_index]; + } + + StringRefNull name() const + { + return m_signature.function_name; + } + + const MFSignature &signature() const + { + return m_signature; + } + + protected: + MFSignatureBuilder get_builder(StringRef function_name) + { + m_signature.function_name = function_name; + return MFSignatureBuilder(m_signature); + } +}; + +inline MFParamsBuilder::MFParamsBuilder(const class MultiFunction &fn, uint min_array_size) + : MFParamsBuilder(fn.signature(), min_array_size) +{ +} + +} // namespace fn +} // namespace blender + +#endif /* __FN_MULTI_FUNCTION_HH__ */ diff --git a/source/blender/functions/FN_multi_function_context.hh b/source/blender/functions/FN_multi_function_context.hh new file mode 100644 index 00000000000..d1283639467 --- /dev/null +++ b/source/blender/functions/FN_multi_function_context.hh @@ -0,0 +1,49 @@ +/* + * 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_MULTI_FUNCTION_CONTEXT_HH__ +#define __FN_MULTI_FUNCTION_CONTEXT_HH__ + +/** \file + * \ingroup fn + * + * An MFContext is passed along with every call to a multi-function. Right now it does nothing, but + * it can be used for the following purposes: + * - Pass debug information up and down the function call stack. + * - Pass reusable memory buffers to subfunctions to increase performance. + * - Pass cached data to called functions. + */ + +namespace blender { +namespace fn { + +class MFContextBuilder { +}; + +class MFContext { + private: + MFContextBuilder *m_builder; + + public: + MFContext(MFContextBuilder &builder) : m_builder(&builder) + { + } +}; + +} // namespace fn +} // namespace blender + +#endif /* __FN_MULTI_FUNCTION_CONTEXT_HH__ */ diff --git a/source/blender/functions/FN_multi_function_data_type.hh b/source/blender/functions/FN_multi_function_data_type.hh new file mode 100644 index 00000000000..856d1cc7253 --- /dev/null +++ b/source/blender/functions/FN_multi_function_data_type.hh @@ -0,0 +1,125 @@ +/* + * 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_MULTI_FUNCTION_DATA_TYPE_HH__ +#define __FN_MULTI_FUNCTION_DATA_TYPE_HH__ + +/** \file + * \ingroup fn + * + * A MFDataType describes what type of data a multi-function gets as input, outputs or mutates. + * Currently, only individual elements or vectors of elements are supported. Adding more data types + * is possible when necessary. + */ + +#include "FN_cpp_type.hh" + +namespace blender { +namespace fn { + +class MFDataType { + public: + enum Category { + Single, + Vector, + }; + + private: + Category m_category; + const CPPType *m_type; + + MFDataType(Category category, const CPPType &type) : m_category(category), m_type(&type) + { + } + + public: + static MFDataType ForSingle(const CPPType &type) + { + return MFDataType(Single, type); + } + + static MFDataType ForVector(const CPPType &type) + { + return MFDataType(Vector, type); + } + + template<typename T> static MFDataType ForSingle() + { + return MFDataType::ForSingle(CPPType::get<T>()); + } + + template<typename T> static MFDataType ForVector() + { + return MFDataType::ForVector(CPPType::get<T>()); + } + + bool is_single() const + { + return m_category == Single; + } + + bool is_vector() const + { + return m_category == Vector; + } + + Category category() const + { + return m_category; + } + + const CPPType &single_type() const + { + BLI_assert(this->is_single()); + return *m_type; + } + + const CPPType &vector_base_type() const + { + BLI_assert(this->is_vector()); + return *m_type; + } + + friend bool operator==(const MFDataType &a, const MFDataType &b); + friend bool operator!=(const MFDataType &a, const MFDataType &b); + + std::string to_string() const + { + switch (m_category) { + case Single: + return m_type->name(); + case Vector: + return m_type->name() + " Vector"; + } + BLI_assert(false); + return ""; + } +}; + +inline bool operator==(const MFDataType &a, const MFDataType &b) +{ + return a.m_category == b.m_category && a.m_type == b.m_type; +} + +inline bool operator!=(const MFDataType &a, const MFDataType &b) +{ + return !(a == b); +} + +} // namespace fn +} // namespace blender + +#endif /* __FN_MULTI_FUNCTION_DATA_TYPE_HH__ */ diff --git a/source/blender/functions/FN_multi_function_param_type.hh b/source/blender/functions/FN_multi_function_param_type.hh new file mode 100644 index 00000000000..bd31a793b21 --- /dev/null +++ b/source/blender/functions/FN_multi_function_param_type.hh @@ -0,0 +1,155 @@ +/* + * 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_MULTI_FUNCTION_PARAM_TYPE_HH__ +#define __FN_MULTI_FUNCTION_PARAM_TYPE_HH__ + +/** \file + * \ingroup fn + * + * A multi-function has an arbitrary amount of parameters. Every parameter belongs to one of three + * interface types: + * - Input: An input parameter is readonly inside the function. The values have to be provided by + * the caller. + * - Output: An output parameter has to be initialized by the function. However, the caller + * provides the memory where the data has to be constructed. + * - Mutable: A mutable parameter can be considered to be an input and output. The caller has to + * initialize the data, but the function is allowed to modify it. + * + * Furthermore, every parameter has a MFDataType that describes what kind of data is being passed + * around. + */ + +#include "FN_multi_function_data_type.hh" + +namespace blender { +namespace fn { + +class MFParamType { + public: + enum InterfaceType { + Input, + Output, + Mutable, + }; + + enum Category { + SingleInput, + VectorInput, + SingleOutput, + VectorOutput, + SingleMutable, + VectorMutable, + }; + + private: + InterfaceType m_interface_type; + MFDataType m_data_type; + + public: + MFParamType(InterfaceType interface_type, MFDataType data_type) + : m_interface_type(interface_type), m_data_type(data_type) + { + } + + static MFParamType ForSingleInput(const CPPType &type) + { + return MFParamType(InterfaceType::Input, MFDataType::ForSingle(type)); + } + + static MFParamType ForVectorInput(const CPPType &base_type) + { + return MFParamType(InterfaceType::Input, MFDataType::ForVector(base_type)); + } + + static MFParamType ForSingleOutput(const CPPType &type) + { + return MFParamType(InterfaceType::Output, MFDataType::ForSingle(type)); + } + + static MFParamType ForVectorOutput(const CPPType &base_type) + { + return MFParamType(InterfaceType::Output, MFDataType::ForVector(base_type)); + } + + static MFParamType ForMutableSingle(const CPPType &type) + { + return MFParamType(InterfaceType::Mutable, MFDataType::ForSingle(type)); + } + + static MFParamType ForMutableVector(const CPPType &base_type) + { + return MFParamType(InterfaceType::Mutable, MFDataType::ForVector(base_type)); + } + + MFDataType data_type() const + { + return m_data_type; + } + + InterfaceType interface_type() const + { + return m_interface_type; + } + + Category category() const + { + switch (m_data_type.category()) { + case MFDataType::Single: { + switch (m_interface_type) { + case Input: + return SingleInput; + case Output: + return SingleOutput; + case Mutable: + return SingleMutable; + } + break; + } + case MFDataType::Vector: { + switch (m_interface_type) { + case Input: + return VectorInput; + case Output: + return VectorOutput; + case Mutable: + return VectorMutable; + } + break; + } + } + BLI_assert(false); + return SingleInput; + } + + friend bool operator==(const MFParamType &a, const MFParamType &b); + friend bool operator!=(const MFParamType &a, const MFParamType &b); +}; + +inline bool operator==(const MFParamType &a, const MFParamType &b) +{ + return a.m_interface_type == b.m_interface_type && a.m_data_type == b.m_data_type; +} + +inline bool operator!=(const MFParamType &a, const MFParamType &b) +{ + return !(a == b); +} + +} // namespace fn +} // namespace blender + +#endif /* __FN_MULTI_FUNCTION_PARAM_TYPE_HH__ */ diff --git a/source/blender/functions/FN_multi_function_params.hh b/source/blender/functions/FN_multi_function_params.hh new file mode 100644 index 00000000000..2388e3b15e5 --- /dev/null +++ b/source/blender/functions/FN_multi_function_params.hh @@ -0,0 +1,236 @@ +/* + * 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_MULTI_FUNCTION_PARAMS_HH__ +#define __FN_MULTI_FUNCTION_PARAMS_HH__ + +/** \file + * \ingroup fn + * + * This file provides an MFParams and MFParamsBuilder structure. + * + * `MFParamsBuilder` is used by a function caller to be prepare all parameters that are passed into + * the function. `MFParams` is then used inside the called function to access the parameters. + */ + +#include "FN_generic_vector_array.hh" +#include "FN_multi_function_signature.hh" + +namespace blender { +namespace fn { + +class MFParamsBuilder { + private: + const MFSignature *m_signature; + uint m_min_array_size; + Vector<GVSpan> m_virtual_spans; + Vector<GMutableSpan> m_mutable_spans; + Vector<GVArraySpan> m_virtual_array_spans; + Vector<GVectorArray *> m_vector_arrays; + + friend class MFParams; + + public: + MFParamsBuilder(const MFSignature &signature, uint min_array_size) + : m_signature(&signature), m_min_array_size(min_array_size) + { + } + + MFParamsBuilder(const class MultiFunction &fn, uint min_array_size); + + template<typename T> void add_readonly_single_input(const T *value) + { + this->add_readonly_single_input( + GVSpan::FromSingle(CPPType::get<T>(), value, m_min_array_size)); + } + void add_readonly_single_input(GVSpan ref) + { + this->assert_current_param_type(MFParamType::ForSingleInput(ref.type())); + BLI_assert(ref.size() >= m_min_array_size); + m_virtual_spans.append(ref); + } + + void add_readonly_vector_input(GVArraySpan ref) + { + this->assert_current_param_type(MFParamType::ForVectorInput(ref.type())); + BLI_assert(ref.size() >= m_min_array_size); + m_virtual_array_spans.append(ref); + } + + void add_uninitialized_single_output(GMutableSpan ref) + { + this->assert_current_param_type(MFParamType::ForSingleOutput(ref.type())); + BLI_assert(ref.size() >= m_min_array_size); + m_mutable_spans.append(ref); + } + + void add_vector_output(GVectorArray &vector_array) + { + this->assert_current_param_type(MFParamType::ForVectorOutput(vector_array.type())); + BLI_assert(vector_array.size() >= m_min_array_size); + m_vector_arrays.append(&vector_array); + } + + void add_single_mutable(GMutableSpan ref) + { + this->assert_current_param_type(MFParamType::ForMutableSingle(ref.type())); + BLI_assert(ref.size() >= m_min_array_size); + m_mutable_spans.append(ref); + } + + void add_vector_mutable(GVectorArray &vector_array) + { + this->assert_current_param_type(MFParamType::ForMutableVector(vector_array.type())); + BLI_assert(vector_array.size() >= m_min_array_size); + m_vector_arrays.append(&vector_array); + } + + GMutableSpan computed_array(uint param_index) + { + BLI_assert(ELEM(m_signature->param_types[param_index].category(), + MFParamType::SingleOutput, + MFParamType::SingleMutable)); + uint data_index = m_signature->data_index(param_index); + return m_mutable_spans[data_index]; + } + + GVectorArray &computed_vector_array(uint param_index) + { + BLI_assert(ELEM(m_signature->param_types[param_index].category(), + MFParamType::VectorOutput, + MFParamType::VectorMutable)); + uint data_index = m_signature->data_index(param_index); + return *m_vector_arrays[data_index]; + } + + private: + void assert_current_param_type(MFParamType param_type) + { + UNUSED_VARS_NDEBUG(param_type); +#ifdef DEBUG + uint param_index = this->current_param_index(); + MFParamType expected_type = m_signature->param_types[param_index]; + BLI_assert(expected_type == param_type); +#endif + } + + uint current_param_index() const + { + return m_virtual_spans.size() + m_mutable_spans.size() + m_virtual_array_spans.size() + + m_vector_arrays.size(); + } +}; + +class MFParams { + private: + MFParamsBuilder *m_builder; + + public: + MFParams(MFParamsBuilder &builder) : m_builder(&builder) + { + } + + template<typename T> VSpan<T> readonly_single_input(uint param_index, StringRef name = "") + { + return this->readonly_single_input(param_index, name).typed<T>(); + } + GVSpan readonly_single_input(uint param_index, StringRef name = "") + { + this->assert_correct_param(param_index, name, MFParamType::SingleInput); + uint data_index = m_builder->m_signature->data_index(param_index); + return m_builder->m_virtual_spans[data_index]; + } + + template<typename T> + MutableSpan<T> uninitialized_single_output(uint param_index, StringRef name = "") + { + return this->uninitialized_single_output(param_index, name).typed<T>(); + } + GMutableSpan uninitialized_single_output(uint param_index, StringRef name = "") + { + this->assert_correct_param(param_index, name, MFParamType::SingleOutput); + uint data_index = m_builder->m_signature->data_index(param_index); + return m_builder->m_mutable_spans[data_index]; + } + + template<typename T> VArraySpan<T> readonly_vector_input(uint param_index, StringRef name = "") + { + return this->readonly_vector_input(param_index, name).typed<T>(); + } + GVArraySpan readonly_vector_input(uint param_index, StringRef name = "") + { + this->assert_correct_param(param_index, name, MFParamType::VectorInput); + uint data_index = m_builder->m_signature->data_index(param_index); + return m_builder->m_virtual_array_spans[data_index]; + } + + template<typename T> GVectorArrayRef<T> vector_output(uint param_index, StringRef name = "") + { + return this->vector_output(param_index, name).typed<T>(); + } + GVectorArray &vector_output(uint param_index, StringRef name = "") + { + this->assert_correct_param(param_index, name, MFParamType::VectorOutput); + uint data_index = m_builder->m_signature->data_index(param_index); + return *m_builder->m_vector_arrays[data_index]; + } + + template<typename T> MutableSpan<T> single_mutable(uint param_index, StringRef name = "") + { + return this->single_mutable(param_index, name).typed<T>(); + } + GMutableSpan single_mutable(uint param_index, StringRef name = "") + { + this->assert_correct_param(param_index, name, MFParamType::SingleMutable); + uint data_index = m_builder->m_signature->data_index(param_index); + return m_builder->m_mutable_spans[data_index]; + } + + GVectorArray &vector_mutable(uint param_index, StringRef name = "") + { + this->assert_correct_param(param_index, name, MFParamType::VectorMutable); + uint data_index = m_builder->m_signature->data_index(param_index); + return *m_builder->m_vector_arrays[data_index]; + } + + private: + void assert_correct_param(uint param_index, StringRef name, MFParamType param_type) + { + UNUSED_VARS_NDEBUG(param_index, name, param_type); +#ifdef DEBUG + BLI_assert(m_builder->m_signature->param_types[param_index] == param_type); + if (name.size() > 0) { + BLI_assert(m_builder->m_signature->param_names[param_index] == name); + } +#endif + } + + void assert_correct_param(uint param_index, StringRef name, MFParamType::Category category) + { + UNUSED_VARS_NDEBUG(param_index, name, category); +#ifdef DEBUG + BLI_assert(m_builder->m_signature->param_types[param_index].category() == category); + if (name.size() > 0) { + BLI_assert(m_builder->m_signature->param_names[param_index] == name); + } +#endif + } +}; + +} // namespace fn +} // namespace blender + +#endif /* __FN_MULTI_FUNCTION_PARAMS_HH__ */ diff --git a/source/blender/functions/FN_multi_function_signature.hh b/source/blender/functions/FN_multi_function_signature.hh new file mode 100644 index 00000000000..64bbd7be5c8 --- /dev/null +++ b/source/blender/functions/FN_multi_function_signature.hh @@ -0,0 +1,161 @@ +/* + * 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_MULTI_FUNCTION_SIGNATURE_HH__ +#define __FN_MULTI_FUNCTION_SIGNATURE_HH__ + +/** \file + * \ingroup fn + * + * The signature of a multi-function contains the functions name and expected parameters. New + * signatures should be build using the MFSignatureBuilder class. + */ + +#include "FN_multi_function_param_type.hh" + +#include "BLI_vector.hh" + +namespace blender { +namespace fn { + +struct MFSignature { + std::string function_name; + Vector<std::string> param_names; + Vector<MFParamType> param_types; + Vector<uint> param_data_indices; + + uint data_index(uint param_index) const + { + return param_data_indices[param_index]; + } +}; + +class MFSignatureBuilder { + private: + MFSignature &m_data; + uint m_span_count = 0; + uint m_virtual_span_count = 0; + uint m_virtual_array_span_count = 0; + uint m_vector_array_count = 0; + + public: + MFSignatureBuilder(MFSignature &data) : m_data(data) + { + BLI_assert(data.param_names.is_empty()); + BLI_assert(data.param_types.is_empty()); + BLI_assert(data.param_data_indices.is_empty()); + } + + /* Input Param Types */ + + template<typename T> void single_input(StringRef name) + { + this->single_input(name, CPPType::get<T>()); + } + void single_input(StringRef name, const CPPType &type) + { + this->input(name, MFDataType::ForSingle(type)); + } + template<typename T> void vector_input(StringRef name) + { + this->vector_input(name, CPPType::get<T>()); + } + void vector_input(StringRef name, const CPPType &base_type) + { + this->input(name, MFDataType::ForVector(base_type)); + } + void input(StringRef name, MFDataType data_type) + { + m_data.param_names.append(name); + m_data.param_types.append(MFParamType(MFParamType::Input, data_type)); + + switch (data_type.category()) { + case MFDataType::Single: + m_data.param_data_indices.append(m_virtual_span_count++); + break; + case MFDataType::Vector: + m_data.param_data_indices.append(m_virtual_array_span_count++); + break; + } + } + + /* Output Param Types */ + + template<typename T> void single_output(StringRef name) + { + this->single_output(name, CPPType::get<T>()); + } + void single_output(StringRef name, const CPPType &type) + { + this->output(name, MFDataType::ForSingle(type)); + } + template<typename T> void vector_output(StringRef name) + { + this->vector_output(name, CPPType::get<T>()); + } + void vector_output(StringRef name, const CPPType &base_type) + { + this->output(name, MFDataType::ForVector(base_type)); + } + void output(StringRef name, MFDataType data_type) + { + m_data.param_names.append(name); + m_data.param_types.append(MFParamType(MFParamType::Output, data_type)); + + switch (data_type.category()) { + case MFDataType::Single: + m_data.param_data_indices.append(m_span_count++); + break; + case MFDataType::Vector: + m_data.param_data_indices.append(m_vector_array_count++); + break; + } + } + + /* Mutable Param Types */ + + template<typename T> void single_mutable(StringRef name) + { + this->single_mutable(name, CPPType::get<T>()); + } + void single_mutable(StringRef name, const CPPType &type) + { + this->mutable_(name, MFDataType::ForSingle(type)); + } + void vector_mutable(StringRef name, const CPPType &base_type) + { + this->mutable_(name, MFDataType::ForVector(base_type)); + } + void mutable_(StringRef name, MFDataType data_type) + { + m_data.param_names.append(name); + m_data.param_types.append(MFParamType(MFParamType::Mutable, data_type)); + + switch (data_type.category()) { + case MFDataType::Single: + m_data.param_data_indices.append(m_span_count++); + break; + case MFDataType::Vector: + m_data.param_data_indices.append(m_vector_array_count++); + break; + } + } +}; + +} // namespace fn +} // namespace blender + +#endif /* __FN_MULTI_FUNCTION_SIGNATURE_HH__ */ diff --git a/source/blender/functions/FN_spans.hh b/source/blender/functions/FN_spans.hh new file mode 100644 index 00000000000..c206662231c --- /dev/null +++ b/source/blender/functions/FN_spans.hh @@ -0,0 +1,391 @@ +/* + * 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<T>. + * 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<T> has to be known at compile time. + * 2. Span<T> 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 { +namespace fn { + +/** + * A generic span. It behaves just like a blender::Span<T>, but the type is only known at run-time. + */ +class GSpan { + private: + const CPPType *m_type; + const void *m_buffer; + uint m_size; + + public: + GSpan(const CPPType &type, const void *buffer, uint size) + : m_type(&type), m_buffer(buffer), m_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<typename T> + GSpan(Span<T> array) : GSpan(CPPType::get<T>(), (const void *)array.begin(), array.size()) + { + } + + const CPPType &type() const + { + return *m_type; + } + + bool is_empty() const + { + return m_size == 0; + } + + uint size() const + { + return m_size; + } + + const void *buffer() const + { + return m_buffer; + } + + const void *operator[](uint index) const + { + BLI_assert(index < m_size); + return POINTER_OFFSET(m_buffer, m_type->size() * index); + } + + template<typename T> Span<T> typed() const + { + BLI_assert(CPPType::get<T>() == *m_type); + return Span<T>((const T *)m_buffer, m_size); + } +}; + +/** + * A generic mutable span. It behaves just like a blender::MutableSpan<T>, but the type is only + * known at run-time. + */ +class GMutableSpan { + private: + const CPPType *m_type; + void *m_buffer; + uint m_size; + + public: + GMutableSpan(const CPPType &type, void *buffer, uint size) + : m_type(&type), m_buffer(buffer), m_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<typename T> + GMutableSpan(MutableSpan<T> array) + : GMutableSpan(CPPType::get<T>(), (void *)array.begin(), array.size()) + { + } + + operator GSpan() const + { + return GSpan(*m_type, m_buffer, m_size); + } + + const CPPType &type() const + { + return *m_type; + } + + bool is_empty() const + { + return m_size == 0; + } + + uint size() const + { + return m_size; + } + + void *buffer() + { + return m_buffer; + } + + void *operator[](uint index) + { + BLI_assert(index < m_size); + return POINTER_OFFSET(m_buffer, m_type->size() * index); + } + + template<typename T> MutableSpan<T> typed() + { + BLI_assert(CPPType::get<T>() == *m_type); + return MutableSpan<T>((T *)m_buffer, m_size); + } +}; + +/** + * A virtual span. It behaves like a blender::Span<T>, but might not be backed up by an actual + * array. + */ +template<typename T> class VSpan { + private: + enum Category { + Single, + FullArray, + FullPointerArray, + }; + + uint m_virtual_size; + Category m_category; + + union { + struct { + const T *data; + } single; + struct { + const T *data; + } full_array; + struct { + const T *const *data; + } full_pointer_array; + } m_data; + + public: + VSpan() + { + m_virtual_size = 0; + m_category = FullArray; + m_data.full_array.data = nullptr; + } + + VSpan(Span<T> values) + { + m_virtual_size = values.size(); + m_category = FullArray; + m_data.full_array.data = values.begin(); + } + + VSpan(MutableSpan<T> values) : VSpan(Span<T>(values)) + { + } + + VSpan(Span<const T *> values) + { + m_virtual_size = values.size(); + m_category = FullPointerArray; + m_data.full_pointer_array.data = values.begin(); + } + + static VSpan FromSingle(const T *value, uint virtual_size) + { + VSpan ref; + ref.m_virtual_size = virtual_size; + ref.m_category = Single; + ref.m_data.single.data = value; + return ref; + } + + const T &operator[](uint index) const + { + BLI_assert(index < m_virtual_size); + switch (m_category) { + case Single: + return *m_data.single.data; + case FullArray: + return m_data.full_array.data[index]; + case FullPointerArray: + return *m_data.full_pointer_array.data[index]; + } + BLI_assert(false); + return *m_data.single.data; + } + + bool is_empty() const + { + return m_virtual_size == 0; + } + + uint size() const + { + return m_virtual_size; + } +}; + +/** + * A generic virtual span. It behaves like a blender::Span<T>, but the type is only known at + * run-time and it might not be backed up by an actual array. + */ +class GVSpan { + private: + enum Category { + Single, + FullArray, + FullPointerArray, + }; + + const CPPType *m_type; + uint m_virtual_size; + Category m_category; + + union { + struct { + const void *data; + } single; + struct { + const void *data; + } full_array; + struct { + const void *const *data; + } full_pointer_array; + } m_data; + + GVSpan() = default; + + public: + GVSpan(const CPPType &type) + { + m_type = &type; + m_virtual_size = 0; + m_category = FullArray; + m_data.full_array.data = nullptr; + } + + GVSpan(GSpan values) + { + m_type = &values.type(); + m_virtual_size = values.size(); + m_category = FullArray; + m_data.full_array.data = values.buffer(); + } + + GVSpan(GMutableSpan values) : GVSpan(GSpan(values)) + { + } + + template<typename T> GVSpan(Span<T> values) : GVSpan(GSpan(values)) + { + } + + template<typename T> GVSpan(MutableSpan<T> values) : GVSpan(GSpan(values)) + { + } + + static GVSpan FromSingle(const CPPType &type, const void *value, uint virtual_size) + { + GVSpan ref; + ref.m_type = &type; + ref.m_virtual_size = virtual_size; + ref.m_category = Single; + ref.m_data.single.data = value; + return ref; + } + + static GVSpan FromFullPointerArray(const CPPType &type, const void *const *values, uint size) + { + GVSpan ref; + ref.m_type = &type; + ref.m_virtual_size = size; + ref.m_category = FullPointerArray; + ref.m_data.full_pointer_array.data = values; + return ref; + } + + bool is_empty() const + { + return m_virtual_size == 0; + } + + uint size() const + { + return m_virtual_size; + } + + const CPPType &type() const + { + return *m_type; + } + + const void *operator[](uint index) const + { + BLI_assert(index < m_virtual_size); + switch (m_category) { + case Single: + return m_data.single.data; + case FullArray: + return POINTER_OFFSET(m_data.full_array.data, index * m_type->size()); + case FullPointerArray: + return m_data.full_pointer_array.data[index]; + } + BLI_assert(false); + return m_data.single.data; + } + + template<typename T> VSpan<T> typed() const + { + BLI_assert(CPPType::get<T>() == *m_type); + switch (m_category) { + case Single: + return VSpan<T>::FromSingle((const T *)m_data.single.data, m_virtual_size); + case FullArray: + return VSpan<T>(Span<T>((const T *)m_data.full_array.data, m_virtual_size)); + case FullPointerArray: + return VSpan<T>( + Span<const T *>((const T *const *)m_data.full_pointer_array.data, m_virtual_size)); + } + BLI_assert(false); + return {}; + } +}; + +} // namespace fn +} // namespace blender + +#endif /* __FN_SPANS_HH__ */ diff --git a/source/blender/functions/intern/cpp_types.cc b/source/blender/functions/intern/cpp_types.cc index 6339250caa5..cb4b065e5bc 100644 --- a/source/blender/functions/intern/cpp_types.cc +++ b/source/blender/functions/intern/cpp_types.cc @@ -22,7 +22,7 @@ #include "BLI_float4x4.hh" namespace blender { -namespace FN { +namespace fn { MAKE_CPP_TYPE(bool, bool) @@ -39,5 +39,5 @@ MAKE_CPP_TYPE(Color4b, blender::Color4b) MAKE_CPP_TYPE(string, std::string) -} // namespace FN +} // namespace fn } // namespace blender diff --git a/tests/gtests/functions/CMakeLists.txt b/tests/gtests/functions/CMakeLists.txt index 413761ed0c8..f0fe867ea3c 100644 --- a/tests/gtests/functions/CMakeLists.txt +++ b/tests/gtests/functions/CMakeLists.txt @@ -36,4 +36,8 @@ if(WITH_BUILDINFO) set(BUILDINFO buildinfoobj) endif() +BLENDER_TEST(FN_array_spans "bf_blenlib;bf_functions;${BUILDINFO}") BLENDER_TEST(FN_cpp_type "bf_blenlib;bf_functions;${BUILDINFO}") +BLENDER_TEST(FN_generic_vector_array "bf_blenlib;bf_functions;${BUILDINFO}") +BLENDER_TEST(FN_multi_function "bf_blenlib;bf_functions;${BUILDINFO}") +BLENDER_TEST(FN_spans "bf_blenlib;bf_functions;${BUILDINFO}") diff --git a/tests/gtests/functions/FN_array_spans_test.cc b/tests/gtests/functions/FN_array_spans_test.cc new file mode 100644 index 00000000000..47e1c129266 --- /dev/null +++ b/tests/gtests/functions/FN_array_spans_test.cc @@ -0,0 +1,92 @@ +/* + * 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. + */ + +#include "testing/testing.h" + +#include "FN_array_spans.hh" +#include "FN_cpp_types.hh" + +namespace blender { +namespace fn { + +TEST(virtual_array_span, EmptyConstructor) +{ + VArraySpan<int> span; + EXPECT_EQ(span.size(), 0); + EXPECT_TRUE(span.is_empty()); +} + +TEST(virtual_array_span, SingleArrayConstructor) +{ + std::array<int, 4> values = {3, 4, 5, 6}; + VArraySpan<int> span{Span<int>(values), 3}; + EXPECT_EQ(span.size(), 3); + EXPECT_FALSE(span.is_empty()); + EXPECT_EQ(span[0].size(), 4); + EXPECT_EQ(span[1].size(), 4); + EXPECT_EQ(span[2].size(), 4); + EXPECT_EQ(span[0][0], 3); + EXPECT_EQ(span[0][1], 4); + EXPECT_EQ(span[0][2], 5); + EXPECT_EQ(span[0][3], 6); + EXPECT_EQ(span[1][3], 6); + EXPECT_EQ(span[2][1], 4); +} + +TEST(virtual_array_span, MultipleArrayConstructor) +{ + std::array<int, 4> values0 = {1, 2, 3, 4}; + std::array<int, 2> values1 = {6, 7}; + std::array<int, 1> values2 = {8}; + std::array<const int *, 3> starts = {values0.data(), values1.data(), values2.data()}; + std::array<uint, 3> sizes{values0.size(), values1.size(), values2.size()}; + + VArraySpan<int> span{starts, sizes}; + EXPECT_EQ(span.size(), 3); + EXPECT_FALSE(span.is_empty()); + EXPECT_EQ(span[0].size(), 4); + EXPECT_EQ(span[1].size(), 2); + EXPECT_EQ(span[2].size(), 1); + EXPECT_EQ(&span[0][0], values0.data()); + EXPECT_EQ(&span[1][0], values1.data()); + EXPECT_EQ(&span[2][0], values2.data()); + EXPECT_EQ(span[2][0], 8); + EXPECT_EQ(span[1][1], 7); +} + +TEST(generic_virtual_array_span, TypeConstructor) +{ + GVArraySpan span{CPPType_int32}; + EXPECT_EQ(span.size(), 0); + EXPECT_TRUE(span.is_empty()); +} + +TEST(generic_virtual_array_span, GSpanConstructor) +{ + std::array<std::string, 3> values = {"hello", "world", "test"}; + GVArraySpan span{GSpan(CPPType_string, values.data(), 3), 5}; + EXPECT_EQ(span.size(), 5); + EXPECT_FALSE(span.is_empty()); + EXPECT_EQ(span[0][0], values.data()); + EXPECT_EQ(span[1][0], values.data()); + EXPECT_EQ(span[4][0], values.data()); + EXPECT_EQ(span[0].size(), 3); + EXPECT_EQ(span[2].size(), 3); + EXPECT_EQ(*(std::string *)span[3][1], "world"); +} + +} // namespace fn +} // namespace blender diff --git a/tests/gtests/functions/FN_cpp_type_test.cc b/tests/gtests/functions/FN_cpp_type_test.cc index 811e1a5d783..83dc327e381 100644 --- a/tests/gtests/functions/FN_cpp_type_test.cc +++ b/tests/gtests/functions/FN_cpp_type_test.cc @@ -12,7 +12,6 @@ * 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. - * */ #include "testing/testing.h" @@ -20,7 +19,7 @@ #include "FN_cpp_type.hh" namespace blender { -namespace FN { +namespace fn { static const int default_constructed_value = 1; static const int copy_constructed_value = 2; @@ -301,5 +300,5 @@ TEST(cpp_type, FillUninitialized) EXPECT_EQ(buffer2[9], 0); } -} // namespace FN +} // namespace fn } // namespace blender diff --git a/tests/gtests/functions/FN_generic_vector_array_test.cc b/tests/gtests/functions/FN_generic_vector_array_test.cc new file mode 100644 index 00000000000..5840feb955d --- /dev/null +++ b/tests/gtests/functions/FN_generic_vector_array_test.cc @@ -0,0 +1,101 @@ +/* + * 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. + */ + +#include "FN_cpp_types.hh" +#include "FN_generic_vector_array.hh" + +#include "testing/testing.h" + +namespace blender { +namespace fn { + +TEST(generic_vector_array, Constructor) +{ + GVectorArray vectors{CPPType_int32, 3}; + EXPECT_EQ(vectors.size(), 3); + EXPECT_EQ(vectors.lengths().size(), 3); + EXPECT_EQ(vectors.starts().size(), 3); + EXPECT_EQ(vectors.lengths()[0], 0); + EXPECT_EQ(vectors.lengths()[1], 0); + EXPECT_EQ(vectors.lengths()[2], 0); + EXPECT_EQ(vectors.type(), CPPType_int32); +} + +TEST(generic_vector_array, Append) +{ + GVectorArray vectors{CPPType_string, 3}; + std::string value = "hello"; + vectors.append(0, &value); + value = "world"; + vectors.append(0, &value); + vectors.append(2, &value); + + EXPECT_EQ(vectors.lengths()[0], 2); + EXPECT_EQ(vectors.lengths()[1], 0); + EXPECT_EQ(vectors.lengths()[2], 1); + EXPECT_EQ(vectors[0].size(), 2); + EXPECT_EQ(vectors[0].typed<std::string>()[0], "hello"); + EXPECT_EQ(vectors[0].typed<std::string>()[1], "world"); + EXPECT_EQ(vectors[2].typed<std::string>()[0], "world"); +} + +TEST(generic_vector_array, AsArraySpan) +{ + GVectorArray vectors{CPPType_int32, 3}; + int value = 3; + vectors.append(0, &value); + vectors.append(0, &value); + value = 5; + vectors.append(2, &value); + vectors.append(2, &value); + vectors.append(2, &value); + + GVArraySpan span = vectors; + EXPECT_EQ(span.type(), CPPType_int32); + EXPECT_EQ(span.size(), 3); + EXPECT_EQ(span[0].size(), 2); + EXPECT_EQ(span[1].size(), 0); + EXPECT_EQ(span[2].size(), 3); + EXPECT_EQ(span[0].typed<int>()[1], 3); + EXPECT_EQ(span[2].typed<int>()[0], 5); +} + +TEST(generic_vector_array, TypedRef) +{ + GVectorArray vectors{CPPType_int32, 4}; + GVectorArrayRef<int> ref = vectors.typed<int>(); + ref.append(0, 2); + ref.append(0, 6); + ref.append(0, 7); + ref.append(2, 1); + ref.append(2, 1); + ref.append(3, 5); + ref.append(3, 6); + + EXPECT_EQ(ref[0].size(), 3); + EXPECT_EQ(vectors[0].size(), 3); + EXPECT_EQ(ref[0][0], 2); + EXPECT_EQ(ref[0][1], 6); + EXPECT_EQ(ref[0][2], 7); + EXPECT_EQ(ref[1].size(), 0); + EXPECT_EQ(ref[2][0], 1); + EXPECT_EQ(ref[2][1], 1); + EXPECT_EQ(ref[3][0], 5); + EXPECT_EQ(ref[3][1], 6); +} + +} // namespace fn +} // namespace blender diff --git a/tests/gtests/functions/FN_multi_function_test.cc b/tests/gtests/functions/FN_multi_function_test.cc new file mode 100644 index 00000000000..8cd9767abcf --- /dev/null +++ b/tests/gtests/functions/FN_multi_function_test.cc @@ -0,0 +1,222 @@ +/* + * 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. + */ + +#include "testing/testing.h" + +#include "FN_cpp_types.hh" +#include "FN_multi_function.hh" + +namespace blender { +namespace fn { + +class AddFunction : public MultiFunction { + public: + AddFunction() + { + MFSignatureBuilder builder = this->get_builder("Add"); + builder.single_input<int>("A"); + builder.single_input<int>("B"); + builder.single_output<int>("Result"); + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + VSpan<int> a = params.readonly_single_input<int>(0, "A"); + VSpan<int> b = params.readonly_single_input<int>(1, "B"); + MutableSpan<int> result = params.uninitialized_single_output<int>(2, "Result"); + + for (uint i : mask) { + result[i] = a[i] + b[i]; + } + } +}; + +TEST(multi_function, AddFunction) +{ + AddFunction fn; + + Array<int> input1 = {4, 5, 6}; + Array<int> input2 = {10, 20, 30}; + Array<int> output(3, -1); + + MFParamsBuilder params(fn, 3); + params.add_readonly_single_input(input1.as_span()); + params.add_readonly_single_input(input2.as_span()); + params.add_uninitialized_single_output(output.as_mutable_span()); + + MFContextBuilder context; + + fn.call({0, 2}, params, context); + + EXPECT_EQ(output[0], 14); + EXPECT_EQ(output[1], -1); + EXPECT_EQ(output[2], 36); +} + +class AddPrefixFunction : public MultiFunction { + public: + AddPrefixFunction() + { + MFSignatureBuilder builder = this->get_builder("Add Prefix"); + builder.single_input<std::string>("Prefix"); + builder.single_mutable<std::string>("Strings"); + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + VSpan<std::string> prefixes = params.readonly_single_input<std::string>(0, "Prefix"); + MutableSpan<std::string> strings = params.single_mutable<std::string>(1, "Strings"); + + for (uint i : mask) { + strings[i] = prefixes[i] + strings[i]; + } + } +}; + +TEST(multi_function, AddPrefixFunction) +{ + AddPrefixFunction fn; + + Array<std::string> strings = { + "Hello", + "World", + "This is a test", + "Another much longer string to trigger an allocation", + }; + + std::string prefix = "AB"; + + MFParamsBuilder params(fn, strings.size()); + params.add_readonly_single_input(&prefix); + params.add_single_mutable(strings.as_mutable_span()); + + MFContextBuilder context; + + fn.call({0, 2, 3}, params, context); + + EXPECT_EQ(strings[0], "ABHello"); + EXPECT_EQ(strings[1], "World"); + EXPECT_EQ(strings[2], "ABThis is a test"); + EXPECT_EQ(strings[3], "ABAnother much longer string to trigger an allocation"); +} + +class CreateRangeFunction : public MultiFunction { + public: + CreateRangeFunction() + { + MFSignatureBuilder builder = this->get_builder("Create Range"); + builder.single_input<uint>("Size"); + builder.vector_output<uint>("Range"); + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + VSpan<uint> sizes = params.readonly_single_input<uint>(0, "Size"); + GVectorArrayRef<uint> ranges = params.vector_output<uint>(1, "Range"); + + for (uint i : mask) { + uint size = sizes[i]; + for (uint j : IndexRange(size)) { + ranges.append(i, j); + } + } + } +}; + +TEST(multi_function, CreateRangeFunction) +{ + CreateRangeFunction fn; + + GVectorArray ranges(CPPType_uint32, 5); + GVectorArrayRef<uint> ranges_ref(ranges); + Array<uint> sizes = {3, 0, 6, 1, 4}; + + MFParamsBuilder params(fn, ranges.size()); + params.add_readonly_single_input(sizes.as_span()); + params.add_vector_output(ranges); + + MFContextBuilder context; + + fn.call({0, 1, 2, 3}, params, context); + + EXPECT_EQ(ranges_ref[0].size(), 3); + EXPECT_EQ(ranges_ref[1].size(), 0); + EXPECT_EQ(ranges_ref[2].size(), 6); + EXPECT_EQ(ranges_ref[3].size(), 1); + EXPECT_EQ(ranges_ref[4].size(), 0); + + EXPECT_EQ(ranges_ref[0][0], 0); + EXPECT_EQ(ranges_ref[0][1], 1); + EXPECT_EQ(ranges_ref[0][2], 2); + EXPECT_EQ(ranges_ref[2][0], 0); + EXPECT_EQ(ranges_ref[2][1], 1); +} + +class GenericAppendFunction : public MultiFunction { + public: + GenericAppendFunction(const CPPType &type) + { + MFSignatureBuilder builder = this->get_builder("Append"); + builder.vector_mutable("Vector", type); + builder.single_input("Value", type); + } + + void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override + { + GVectorArray &vectors = params.vector_mutable(0, "Vector"); + GVSpan values = params.readonly_single_input(1, "Value"); + + for (uint i : mask) { + vectors.append(i, values[i]); + } + } +}; + +TEST(multi_function, GenericAppendFunction) +{ + GenericAppendFunction fn(CPPType_int32); + + GVectorArray vectors(CPPType_int32, 4); + GVectorArrayRef<int> vectors_ref(vectors); + vectors_ref.append(0, 1); + vectors_ref.append(0, 2); + vectors_ref.append(2, 6); + Array<int> values = {5, 7, 3, 1}; + + MFParamsBuilder params(fn, vectors.size()); + params.add_vector_mutable(vectors); + params.add_readonly_single_input(values.as_span()); + + MFContextBuilder context; + + fn.call(IndexRange(vectors.size()), params, context); + + EXPECT_EQ(vectors_ref[0].size(), 3); + EXPECT_EQ(vectors_ref[1].size(), 1); + EXPECT_EQ(vectors_ref[2].size(), 2); + EXPECT_EQ(vectors_ref[3].size(), 1); + + EXPECT_EQ(vectors_ref[0][0], 1); + EXPECT_EQ(vectors_ref[0][1], 2); + EXPECT_EQ(vectors_ref[0][2], 5); + EXPECT_EQ(vectors_ref[1][0], 7); + EXPECT_EQ(vectors_ref[2][0], 6); + EXPECT_EQ(vectors_ref[2][1], 3); + EXPECT_EQ(vectors_ref[3][0], 1); +} + +} // namespace fn +} // namespace blender diff --git a/tests/gtests/functions/FN_spans_test.cc b/tests/gtests/functions/FN_spans_test.cc new file mode 100644 index 00000000000..551918a3aae --- /dev/null +++ b/tests/gtests/functions/FN_spans_test.cc @@ -0,0 +1,159 @@ +/* + * 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. + */ + +#include "testing/testing.h" + +#include "FN_cpp_types.hh" +#include "FN_spans.hh" + +namespace blender { +namespace fn { + +TEST(generic_span, TypeConstructor) +{ + GSpan span(CPPType_float); + EXPECT_EQ(span.size(), 0); + EXPECT_EQ(span.typed<float>().size(), 0); + EXPECT_TRUE(span.is_empty()); +} + +TEST(generic_span, BufferAndSizeConstructor) +{ + int values[4] = {6, 7, 3, 2}; + void *buffer = (void *)values; + GSpan span(CPPType_int32, buffer, 4); + EXPECT_EQ(span.size(), 4); + EXPECT_FALSE(span.is_empty()); + EXPECT_EQ(span.typed<int>().size(), 4); + EXPECT_EQ(span[0], &values[0]); + EXPECT_EQ(span[1], &values[1]); + EXPECT_EQ(span[2], &values[2]); + EXPECT_EQ(span[3], &values[3]); +} + +TEST(generic_mutable_span, TypeConstructor) +{ + GMutableSpan span(CPPType_int32); + EXPECT_EQ(span.size(), 0); + EXPECT_TRUE(span.is_empty()); +} + +TEST(generic_mutable_span, BufferAndSizeConstructor) +{ + int values[4] = {4, 7, 3, 5}; + void *buffer = (void *)values; + GMutableSpan span(CPPType_int32, buffer, 4); + EXPECT_EQ(span.size(), 4); + EXPECT_FALSE(span.is_empty()); + EXPECT_EQ(span.typed<int>().size(), 4); + EXPECT_EQ(values[2], 3); + *(int *)span[2] = 10; + EXPECT_EQ(values[2], 10); + span.typed<int>()[2] = 20; + EXPECT_EQ(values[2], 20); +} + +TEST(virtual_span, EmptyConstructor) +{ + VSpan<int> span; + EXPECT_EQ(span.size(), 0); + EXPECT_TRUE(span.is_empty()); +} + +TEST(virtual_span, SpanConstructor) +{ + std::array<int, 5> values = {7, 3, 8, 6, 4}; + Span<int> span = values; + VSpan<int> virtual_span = span; + EXPECT_EQ(virtual_span.size(), 5); + EXPECT_FALSE(virtual_span.is_empty()); + EXPECT_EQ(virtual_span[0], 7); + EXPECT_EQ(virtual_span[2], 8); + EXPECT_EQ(virtual_span[3], 6); +} + +TEST(virtual_span, PointerSpanConstructor) +{ + int x0 = 3; + int x1 = 6; + int x2 = 7; + std::array<const int *, 3> pointers = {&x0, &x2, &x1}; + VSpan<int> span = Span<const int *>(pointers); + EXPECT_EQ(span.size(), 3); + EXPECT_FALSE(span.is_empty()); + EXPECT_EQ(span[0], 3); + EXPECT_EQ(span[1], 7); + EXPECT_EQ(span[2], 6); + EXPECT_EQ(&span[1], &x2); +} + +TEST(virtual_span, SingleConstructor) +{ + int value = 5; + VSpan<int> span = VSpan<int>::FromSingle(&value, 3); + EXPECT_EQ(span.size(), 3); + EXPECT_FALSE(span.is_empty()); + EXPECT_EQ(span[0], 5); + EXPECT_EQ(span[1], 5); + EXPECT_EQ(span[2], 5); + EXPECT_EQ(&span[0], &value); + EXPECT_EQ(&span[1], &value); + EXPECT_EQ(&span[2], &value); +} + +TEST(generic_virtual_span, TypeConstructor) +{ + GVSpan span(CPPType_int32); + EXPECT_EQ(span.size(), 0); + EXPECT_TRUE(span.is_empty()); +} + +TEST(generic_virtual_span, GenericSpanConstructor) +{ + int values[4] = {3, 4, 5, 6}; + GVSpan span{GSpan(CPPType_int32, values, 4)}; + EXPECT_EQ(span.size(), 4); + EXPECT_FALSE(span.is_empty()); + EXPECT_EQ(span[0], &values[0]); + EXPECT_EQ(span[1], &values[1]); + EXPECT_EQ(span[2], &values[2]); + EXPECT_EQ(span[3], &values[3]); +} + +TEST(generic_virtual_span, SpanConstructor) +{ + std::array<int, 3> values = {6, 7, 8}; + GVSpan span{Span<int>(values)}; + EXPECT_EQ(span.type(), CPPType_int32); + EXPECT_EQ(span.size(), 3); + EXPECT_EQ(span[0], &values[0]); + EXPECT_EQ(span[1], &values[1]); + EXPECT_EQ(span[2], &values[2]); +} + +TEST(generic_virtual_span, SingleConstructor) +{ + int value = 5; + GVSpan span = GVSpan::FromSingle(CPPType_int32, &value, 3); + EXPECT_EQ(span.size(), 3); + EXPECT_FALSE(span.is_empty()); + EXPECT_EQ(span[0], &value); + EXPECT_EQ(span[1], &value); + EXPECT_EQ(span[2], &value); +} + +} // namespace fn +} // namespace blender |