Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Eisel <julian@blender.org>2020-07-01 18:13:57 +0300
committerJulian Eisel <julian@blender.org>2020-07-01 18:13:57 +0300
commit0829cebeb024095c268f190c34daa8ae9a5a224c (patch)
tree12ee5a4a1c2a32e12eff47c8eb9bb0ed217791c1 /source/blender/functions
parentcfde6ebf450594faa57c4bfeaecff10fe512c91b (diff)
parent42be3964eb201180f6b0fa1ff6ce43b8c3845bc2 (diff)
Merge branch 'master' into asset-uuid--archivedasset-uuid--archived
Diffstat (limited to 'source/blender/functions')
-rw-r--r--source/blender/functions/CMakeLists.txt16
-rw-r--r--source/blender/functions/FN_array_spans.hh209
-rw-r--r--source/blender/functions/FN_attributes_ref.hh248
-rw-r--r--source/blender/functions/FN_cpp_type.hh73
-rw-r--r--source/blender/functions/FN_cpp_types.hh4
-rw-r--r--source/blender/functions/FN_generic_vector_array.hh208
-rw-r--r--source/blender/functions/FN_multi_function.hh108
-rw-r--r--source/blender/functions/FN_multi_function_builder.hh232
-rw-r--r--source/blender/functions/FN_multi_function_context.hh48
-rw-r--r--source/blender/functions/FN_multi_function_data_type.hh127
-rw-r--r--source/blender/functions/FN_multi_function_network.hh495
-rw-r--r--source/blender/functions/FN_multi_function_network_evaluation.hh66
-rw-r--r--source/blender/functions/FN_multi_function_param_type.hh165
-rw-r--r--source/blender/functions/FN_multi_function_params.hh240
-rw-r--r--source/blender/functions/FN_multi_function_signature.hh166
-rw-r--r--source/blender/functions/FN_spans.hh404
-rw-r--r--source/blender/functions/intern/attributes_ref.cc72
-rw-r--r--source/blender/functions/intern/cpp_types.cc4
-rw-r--r--source/blender/functions/intern/multi_function.cc40
-rw-r--r--source/blender/functions/intern/multi_function_network.cc278
-rw-r--r--source/blender/functions/intern/multi_function_network_evaluation.cc1063
21 files changed, 4227 insertions, 39 deletions
diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt
index 9ce1d3ac2fe..703d3c393e8 100644
--- a/source/blender/functions/CMakeLists.txt
+++ b/source/blender/functions/CMakeLists.txt
@@ -27,10 +27,26 @@ set(INC_SYS
)
set(SRC
+ intern/attributes_ref.cc
intern/cpp_types.cc
+ intern/multi_function.cc
+ intern/multi_function_network.cc
+ intern/multi_function_network_evaluation.cc
+ FN_array_spans.hh
+ FN_attributes_ref.hh
FN_cpp_type.hh
FN_cpp_types.hh
+ FN_multi_function.hh
+ FN_multi_function_builder.hh
+ FN_multi_function_context.hh
+ FN_multi_function_data_type.hh
+ FN_multi_function_network.hh
+ FN_multi_function_network_evaluation.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..acd3e921b50
--- /dev/null
+++ b/source/blender/functions/FN_array_spans.hh
@@ -0,0 +1,209 @@
+/*
+ * 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 {
+
+/**
+ * Depending on the use case, the referenced data might have a different structure. More
+ * categories can be added when necessary.
+ */
+enum class VArraySpanCategory {
+ SingleArray,
+ StartsAndSizes,
+};
+
+template<typename T> class VArraySpanBase {
+ protected:
+ uint m_virtual_size;
+ VArraySpanCategory 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:
+ bool is_single_array() const
+ {
+ switch (m_category) {
+ case VArraySpanCategory::SingleArray:
+ return true;
+ case VArraySpanCategory::StartsAndSizes:
+ return m_virtual_size == 1;
+ }
+ BLI_assert(false);
+ return false;
+ }
+
+ bool is_empty() const
+ {
+ return this->m_virtual_size == 0;
+ }
+
+ uint size() const
+ {
+ return this->m_virtual_size;
+ }
+};
+
+/**
+ * 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 : public VArraySpanBase<T> {
+ private:
+ friend class GVArraySpan;
+
+ VArraySpan(const VArraySpanBase<void> &other)
+ {
+ memcpy(this, &other, sizeof(VArraySpanBase<void>));
+ }
+
+ public:
+ VArraySpan()
+ {
+ this->m_virtual_size = 0;
+ this->m_category = VArraySpanCategory::StartsAndSizes;
+ this->m_data.starts_and_sizes.starts = nullptr;
+ this->m_data.starts_and_sizes.sizes = nullptr;
+ }
+
+ VArraySpan(Span<T> span, uint virtual_size)
+ {
+ this->m_virtual_size = virtual_size;
+ this->m_category = VArraySpanCategory::SingleArray;
+ this->m_data.single_array.start = span.data();
+ this->m_data.single_array.size = span.size();
+ }
+
+ VArraySpan(Span<const T *> starts, Span<uint> sizes)
+ {
+ BLI_assert(starts.size() == sizes.size());
+ this->m_virtual_size = starts.size();
+ this->m_category = VArraySpanCategory::StartsAndSizes;
+ this->m_data.starts_and_sizes.starts = starts.begin();
+ this->m_data.starts_and_sizes.sizes = sizes.begin();
+ }
+
+ VSpan<T> operator[](uint index) const
+ {
+ BLI_assert(index < this->m_virtual_size);
+ switch (this->m_category) {
+ case VArraySpanCategory::SingleArray:
+ return VSpan<T>(Span<T>(this->m_data.single_array.start, this->m_data.single_array.size));
+ case VArraySpanCategory::StartsAndSizes:
+ return VSpan<T>(Span<T>(this->m_data.starts_and_sizes.starts[index],
+ this->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 : public VArraySpanBase<void> {
+ private:
+ const CPPType *m_type;
+
+ GVArraySpan() = default;
+
+ public:
+ GVArraySpan(const CPPType &type)
+ {
+ this->m_type = &type;
+ this->m_virtual_size = 0;
+ this->m_category = VArraySpanCategory::StartsAndSizes;
+ this->m_data.starts_and_sizes.starts = nullptr;
+ this->m_data.starts_and_sizes.sizes = nullptr;
+ }
+
+ GVArraySpan(GSpan array, uint virtual_size)
+ {
+ this->m_type = &array.type();
+ this->m_virtual_size = virtual_size;
+ this->m_category = VArraySpanCategory::SingleArray;
+ this->m_data.single_array.start = array.buffer();
+ this->m_data.single_array.size = array.size();
+ }
+
+ GVArraySpan(const CPPType &type, Span<const void *> starts, Span<uint> sizes)
+ {
+ BLI_assert(starts.size() == sizes.size());
+ this->m_type = &type;
+ this->m_virtual_size = starts.size();
+ this->m_category = VArraySpanCategory::StartsAndSizes;
+ this->m_data.starts_and_sizes.starts = (void **)starts.begin();
+ this->m_data.starts_and_sizes.sizes = sizes.begin();
+ }
+
+ template<typename T> GVArraySpan(VArraySpan<T> other)
+ {
+ this->m_type = &CPPType::get<T>();
+ memcpy(this, &other, sizeof(VArraySpanBase<void>));
+ }
+
+ const CPPType &type() const
+ {
+ return *this->m_type;
+ }
+
+ template<typename T> VArraySpan<T> typed() const
+ {
+ BLI_assert(m_type->is<T>());
+ return VArraySpan<T>(*this);
+ }
+
+ GVSpan operator[](uint index) const
+ {
+ BLI_assert(index < m_virtual_size);
+ switch (m_category) {
+ case VArraySpanCategory::SingleArray:
+ return GVSpan(GSpan(*m_type, m_data.single_array.start, m_data.single_array.size));
+ case VArraySpanCategory::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_attributes_ref.hh b/source/blender/functions/FN_attributes_ref.hh
new file mode 100644
index 00000000000..383b26330bf
--- /dev/null
+++ b/source/blender/functions/FN_attributes_ref.hh
@@ -0,0 +1,248 @@
+/*
+ * 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_ATTRIBUTES_REF_HH__
+#define __FN_ATTRIBUTES_REF_HH__
+
+/** \file
+ * \ingroup fn
+ *
+ * An AttributesRef references multiple arrays of equal length. Each array has a corresponding name
+ * and index.
+ */
+
+#include <optional>
+
+#include "FN_spans.hh"
+
+#include "BLI_linear_allocator.hh"
+#include "BLI_map.hh"
+#include "BLI_utility_mixins.hh"
+#include "BLI_vector_set.hh"
+
+namespace blender {
+namespace fn {
+
+class AttributesInfo;
+
+class AttributesInfoBuilder : NonCopyable, NonMovable {
+ private:
+ LinearAllocator<> m_allocator;
+ VectorSet<std::string> m_names;
+ Vector<const CPPType *> m_types;
+ Vector<void *> m_defaults;
+
+ friend AttributesInfo;
+
+ public:
+ AttributesInfoBuilder() = default;
+ ~AttributesInfoBuilder();
+
+ template<typename T> void add(StringRef name, const T &default_value)
+ {
+ this->add(name, CPPType::get<T>(), (const void *)&default_value);
+ }
+
+ void add(StringRef name, const CPPType &type, const void *default_value = nullptr);
+};
+
+/**
+ * Stores which attributes are in an AttributesRef. Every attribute has a unique index, a unique
+ * name, a type and a default value.
+ */
+class AttributesInfo : NonCopyable, NonMovable {
+ private:
+ LinearAllocator<> m_allocator;
+ Map<StringRefNull, uint> m_index_by_name;
+ Vector<StringRefNull> m_name_by_index;
+ Vector<const CPPType *> m_type_by_index;
+ Vector<void *> m_defaults;
+
+ public:
+ AttributesInfo() = default;
+ AttributesInfo(const AttributesInfoBuilder &builder);
+ ~AttributesInfo();
+
+ uint size() const
+ {
+ return m_name_by_index.size();
+ }
+
+ IndexRange index_range() const
+ {
+ return m_name_by_index.index_range();
+ }
+
+ StringRefNull name_of(uint index) const
+ {
+ return m_name_by_index[index];
+ }
+
+ uint index_of(StringRef name) const
+ {
+ return m_index_by_name.lookup_as(name);
+ }
+
+ const void *default_of(uint index) const
+ {
+ return m_defaults[index];
+ }
+
+ const void *default_of(StringRef name) const
+ {
+ return this->default_of(this->index_of(name));
+ }
+
+ template<typename T> const T &default_of(uint index) const
+ {
+ BLI_assert(m_type_by_index[index]->is<T>());
+ return *(T *)m_defaults[index];
+ }
+
+ template<typename T> const T &default_of(StringRef name) const
+ {
+ return this->default_of<T>(this->index_of(name));
+ }
+
+ const CPPType &type_of(uint index) const
+ {
+ return *m_type_by_index[index];
+ }
+
+ const CPPType &type_of(StringRef name) const
+ {
+ return this->type_of(this->index_of(name));
+ }
+
+ bool has_attribute(StringRef name, const CPPType &type) const
+ {
+ return this->try_index_of(name, type) >= 0;
+ }
+
+ int try_index_of(StringRef name) const
+ {
+ return (int)m_index_by_name.lookup_default_as(name, -1);
+ }
+
+ int try_index_of(StringRef name, const CPPType &type) const
+ {
+ int index = this->try_index_of(name);
+ if (index == -1) {
+ return -1;
+ }
+ else if (this->type_of((uint)index) == type) {
+ return index;
+ }
+ else {
+ return -1;
+ }
+ }
+};
+
+/**
+ * References multiple arrays that match the description of an AttributesInfo instance. This class
+ * is supposed to be relatively cheap to copy. It does not own any of the arrays itself.
+ */
+class MutableAttributesRef {
+ private:
+ const AttributesInfo *m_info;
+ Span<void *> m_buffers;
+ IndexRange m_range;
+
+ public:
+ MutableAttributesRef(const AttributesInfo &info, Span<void *> buffers, uint size)
+ : MutableAttributesRef(info, buffers, IndexRange(size))
+ {
+ }
+
+ MutableAttributesRef(const AttributesInfo &info, Span<void *> buffers, IndexRange range)
+ : m_info(&info), m_buffers(buffers), m_range(range)
+ {
+ }
+
+ uint size() const
+ {
+ return m_range.size();
+ }
+
+ const AttributesInfo &info() const
+ {
+ return *m_info;
+ }
+
+ GMutableSpan get(uint index) const
+ {
+ const CPPType &type = m_info->type_of(index);
+ void *ptr = POINTER_OFFSET(m_buffers[index], type.size() * m_range.start());
+ return GMutableSpan(type, ptr, m_range.size());
+ }
+
+ GMutableSpan get(StringRef name) const
+ {
+ return this->get(m_info->index_of(name));
+ }
+
+ template<typename T> MutableSpan<T> get(uint index) const
+ {
+ BLI_assert(m_info->type_of(index).is<T>());
+ return MutableSpan<T>((T *)m_buffers[index] + m_range.start(), m_range.size());
+ }
+
+ template<typename T> MutableSpan<T> get(StringRef name) const
+ {
+ return this->get<T>(m_info->index_of(name));
+ }
+
+ std::optional<GMutableSpan> try_get(StringRef name, const CPPType &type) const
+ {
+ int index = m_info->try_index_of(name, type);
+ if (index == -1) {
+ return {};
+ }
+ else {
+ return this->get((uint)index);
+ }
+ }
+
+ template<typename T> std::optional<MutableSpan<T>> try_get(StringRef name) const
+ {
+ int index = m_info->try_index_of(name);
+ if (index == -1) {
+ return {};
+ }
+ else if (m_info->type_of((uint)index).is<T>()) {
+ return this->get<T>((uint)index);
+ }
+ else {
+ return {};
+ }
+ }
+
+ MutableAttributesRef slice(IndexRange range) const
+ {
+ return this->slice(range.start(), range.size());
+ }
+
+ MutableAttributesRef slice(uint start, uint size) const
+ {
+ return MutableAttributesRef(*m_info, m_buffers, m_range.slice(start, size));
+ }
+};
+
+} // namespace fn
+} // namespace blender
+
+#endif /* __FN_ATTRIBUTES_REF_HH__ */
diff --git a/source/blender/functions/FN_cpp_type.hh b/source/blender/functions/FN_cpp_type.hh
index 1dc72e16e77..706ff85bdf3 100644
--- a/source/blender/functions/FN_cpp_type.hh
+++ b/source/blender/functions/FN_cpp_type.hh
@@ -21,7 +21,7 @@
* \ingroup functions
*
* The CPPType class is the core of the runtime-type-system used by the functions system. An
- * instance of this class can represent any C++ type, that is default-constructable, destructable,
+ * instance of this class can represent any C++ type, that is default-constructible, destructible,
* movable and copyable. Therefore it also works for all C types. This restrictions might need to
* be removed in the future, but for now every required type has these properties.
*
@@ -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);
}
@@ -487,6 +483,11 @@ class CPPType {
template<typename T> static const CPPType &get();
+ template<typename T> bool is() const
+ {
+ return this == &CPPType::get<T>();
+ }
+
private:
uint m_size;
uint m_alignment;
@@ -719,16 +720,18 @@ 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>() \
{ \
+ /* This can happen when trying to access a CPPType during static storage initialization. */ \
+ BLI_assert(CPPTYPE_##IDENTIFIER##_owner.get() != nullptr); \
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..6be1b68da4d
--- /dev/null
+++ b/source/blender/functions/FN_generic_vector_array.hh
@@ -0,0 +1,208 @@
+/*
+ * 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]++;
+ }
+
+ void extend(uint index, GVSpan span)
+ {
+ BLI_assert(m_type == span.type());
+ for (uint i = 0; i < span.size(); i++) {
+ this->append(index, span[i]);
+ }
+ }
+
+ void extend(IndexMask mask, GVArraySpan array_span)
+ {
+ BLI_assert(m_type == array_span.type());
+ BLI_assert(mask.min_array_size() <= array_span.size());
+ for (uint i : mask) {
+ this->extend(i, array_span[i]);
+ }
+ }
+
+ 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.is<T>());
+ }
+
+ void append(uint index, const T &value)
+ {
+ m_vector_array->append(index, &value);
+ }
+
+ void extend(uint index, Span<T> values)
+ {
+ m_vector_array->extend(index, values);
+ }
+
+ void extend(uint index, VSpan<T> values)
+ {
+ m_vector_array->extend(index, GVSpan(values));
+ }
+
+ 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..a7e964b6651
--- /dev/null
+++ b/source/blender/functions/FN_multi_function.hh
@@ -0,0 +1,108 @@
+/*
+ * 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(std::string function_name)
+ {
+ m_signature.function_name = std::move(function_name);
+ return MFSignatureBuilder(m_signature);
+ }
+};
+
+inline MFParamsBuilder::MFParamsBuilder(const class MultiFunction &fn, uint min_array_size)
+ : MFParamsBuilder(fn.signature(), min_array_size)
+{
+}
+
+extern const MultiFunction &dummy_multi_function;
+
+} // namespace fn
+} // namespace blender
+
+#endif /* __FN_MULTI_FUNCTION_HH__ */
diff --git a/source/blender/functions/FN_multi_function_builder.hh b/source/blender/functions/FN_multi_function_builder.hh
new file mode 100644
index 00000000000..9fcf31443b2
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_builder.hh
@@ -0,0 +1,232 @@
+/*
+ * 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_BUILDER_HH__
+#define __FN_MULTI_FUNCTION_BUILDER_HH__
+
+/** \file
+ * \ingroup fn
+ *
+ * This file contains several utilities to create multi-functions with less redundant code.
+ */
+
+#include <functional>
+
+#include "FN_multi_function.hh"
+
+namespace blender {
+namespace fn {
+
+/**
+ * Generates a multi-function with the following parameters:
+ * 1. single input (SI) of type In1
+ * 2. single output (SO) of type Out1
+ *
+ * This example creates a function that adds 10 to the incoming values:
+ * CustomMF_SI_SO<int, int> fn("add 10", [](int value) { return value + 10; });
+ */
+template<typename In1, typename Out1> class CustomMF_SI_SO : public MultiFunction {
+ private:
+ using FunctionT = std::function<void(IndexMask, VSpan<In1>, MutableSpan<Out1>)>;
+ FunctionT m_function;
+
+ public:
+ CustomMF_SI_SO(StringRef name, FunctionT function) : m_function(std::move(function))
+ {
+ MFSignatureBuilder signature = this->get_builder(name);
+ signature.single_input<In1>("In1");
+ signature.single_output<Out1>("Out1");
+ }
+
+ template<typename ElementFuncT>
+ CustomMF_SI_SO(StringRef name, ElementFuncT element_fn)
+ : CustomMF_SI_SO(name, CustomMF_SI_SO::create_function(element_fn))
+ {
+ }
+
+ template<typename ElementFuncT> static FunctionT create_function(ElementFuncT element_fn)
+ {
+ return [=](IndexMask mask, VSpan<In1> in1, MutableSpan<Out1> out1) {
+ mask.foreach_index([&](uint i) { new ((void *)&out1[i]) Out1(element_fn(in1[i])); });
+ };
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ VSpan<In1> in1 = params.readonly_single_input<In1>(0);
+ MutableSpan<Out1> out1 = params.uninitialized_single_output<Out1>(1);
+ m_function(mask, in1, out1);
+ }
+};
+
+/**
+ * Generates a multi-function with the following parameters:
+ * 1. single input (SI) of type In1
+ * 2. single input (SI) of type In2
+ * 3. single output (SO) of type Out1
+ */
+template<typename In1, typename In2, typename Out1>
+class CustomMF_SI_SI_SO : public MultiFunction {
+ private:
+ using FunctionT = std::function<void(IndexMask, VSpan<In1>, VSpan<In2>, MutableSpan<Out1>)>;
+ FunctionT m_function;
+
+ public:
+ CustomMF_SI_SI_SO(StringRef name, FunctionT function) : m_function(std::move(function))
+ {
+ MFSignatureBuilder signature = this->get_builder(name);
+ signature.single_input<In1>("In1");
+ signature.single_input<In2>("In2");
+ signature.single_output<Out1>("Out1");
+ }
+
+ template<typename ElementFuncT>
+ CustomMF_SI_SI_SO(StringRef name, ElementFuncT element_fn)
+ : CustomMF_SI_SI_SO(name, CustomMF_SI_SI_SO::create_function(element_fn))
+ {
+ }
+
+ template<typename ElementFuncT> static FunctionT create_function(ElementFuncT element_fn)
+ {
+ return [=](IndexMask mask, VSpan<In1> in1, VSpan<In2> in2, MutableSpan<Out1> out1) {
+ mask.foreach_index([&](uint i) { new ((void *)&out1[i]) Out1(element_fn(in1[i], in2[i])); });
+ };
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ VSpan<In1> in1 = params.readonly_single_input<In1>(0);
+ VSpan<In2> in2 = params.readonly_single_input<In2>(1);
+ MutableSpan<Out1> out1 = params.uninitialized_single_output<Out1>(2);
+ m_function(mask, in1, in2, out1);
+ }
+};
+
+/**
+ * Generates a multi-function with the following parameters:
+ * 1. single input (SI) of type In1
+ * 2. single input (SI) of type In2
+ * 3. single input (SI) of type In3
+ * 4. single output (SO) of type Out1
+ */
+template<typename In1, typename In2, typename In3, typename Out1>
+class CustomMF_SI_SI_SI_SO : public MultiFunction {
+ private:
+ using FunctionT =
+ std::function<void(IndexMask, VSpan<In1>, VSpan<In2>, VSpan<In3>, MutableSpan<Out1>)>;
+ FunctionT m_function;
+
+ public:
+ CustomMF_SI_SI_SI_SO(StringRef name, FunctionT function) : m_function(std::move(function))
+ {
+ MFSignatureBuilder signature = this->get_builder(name);
+ signature.single_input<In1>("In1");
+ signature.single_input<In2>("In2");
+ signature.single_input<In3>("In3");
+ signature.single_output<Out1>("Out1");
+ }
+
+ template<typename ElementFuncT>
+ CustomMF_SI_SI_SI_SO(StringRef name, ElementFuncT element_fn)
+ : CustomMF_SI_SI_SI_SO(name, CustomMF_SI_SI_SI_SO::create_function(element_fn))
+ {
+ }
+
+ template<typename ElementFuncT> static FunctionT create_function(ElementFuncT element_fn)
+ {
+ return [=](IndexMask mask,
+ VSpan<In1> in1,
+ VSpan<In2> in2,
+ VSpan<In3> in3,
+ MutableSpan<Out1> out1) {
+ mask.foreach_index(
+ [&](uint i) { new ((void *)&out1[i]) Out1(element_fn(in1[i], in2[i], in3[i])); });
+ };
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ VSpan<In1> in1 = params.readonly_single_input<In1>(0);
+ VSpan<In2> in2 = params.readonly_single_input<In2>(1);
+ VSpan<In3> in3 = params.readonly_single_input<In3>(2);
+ MutableSpan<Out1> out1 = params.uninitialized_single_output<Out1>(3);
+ m_function(mask, in1, in2, in3, out1);
+ }
+};
+
+/**
+ * Generates a multi-function with the following parameters:
+ * 1. single mutable (SM) of type Mut1
+ */
+template<typename Mut1> class CustomMF_SM : public MultiFunction {
+ private:
+ using FunctionT = std::function<void(IndexMask, MutableSpan<Mut1>)>;
+ FunctionT m_function;
+
+ public:
+ CustomMF_SM(StringRef name, FunctionT function) : m_function(std::move(function))
+ {
+ MFSignatureBuilder signature = this->get_builder(name);
+ signature.single_mutable<Mut1>("Mut1");
+ }
+
+ template<typename ElementFuncT>
+ CustomMF_SM(StringRef name, ElementFuncT element_fn)
+ : CustomMF_SM(name, CustomMF_SM::create_function(element_fn))
+ {
+ }
+
+ template<typename ElementFuncT> static FunctionT create_function(ElementFuncT element_fn)
+ {
+ return [=](IndexMask mask, MutableSpan<Mut1> mut1) {
+ mask.foreach_index([&](uint i) { element_fn(mut1[i]); });
+ };
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ MutableSpan<Mut1> mut1 = params.single_mutable<Mut1>(0);
+ m_function(mask, mut1);
+ }
+};
+
+/**
+ * Generates a multi-function that outputs a constant value.
+ */
+template<typename T> class CustomMF_Constant : public MultiFunction {
+ private:
+ T m_value;
+
+ public:
+ template<typename U> CustomMF_Constant(U &&value) : m_value(std::forward<U>(value))
+ {
+ MFSignatureBuilder signature = this->get_builder("Constant");
+ std::stringstream ss;
+ ss << m_value;
+ signature.single_output<T>(ss.str());
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ MutableSpan<T> output = params.uninitialized_single_output<T>(0);
+ mask.foreach_index([&](uint i) { new (&output[i]) T(m_value); });
+ }
+};
+
+} // namespace fn
+} // namespace blender
+
+#endif /* __FN_MULTI_FUNCTION_BUILDER_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..2fb1cc94812
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_context.hh
@@ -0,0 +1,48 @@
+/*
+ * 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 sub-functions to increase performance.
+ * - Pass cached data to called functions.
+ */
+
+#include "BLI_utildefines.h"
+
+namespace blender {
+namespace fn {
+
+class MFContextBuilder {
+};
+
+class MFContext {
+ public:
+ MFContext(MFContextBuilder &UNUSED(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..1a7b179c6ae
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_data_type.hh
@@ -0,0 +1,127 @@
+/*
+ * 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:
+ MFDataType() = default;
+
+ 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_network.hh b/source/blender/functions/FN_multi_function_network.hh
new file mode 100644
index 00000000000..bb0c870746b
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_network.hh
@@ -0,0 +1,495 @@
+/*
+ * 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_NETWORK_HH__
+#define __FN_MULTI_FUNCTION_NETWORK_HH__
+
+/** \file
+ * \ingroup fn
+ *
+ * A multi-function network (`MFNetwork`) allows you to connect multiple multi-functions. The
+ * `MFNetworkEvaluator` is a multi-function that wraps an entire network into a new multi-function
+ * (which can be used in another network and so on).
+ *
+ * A MFNetwork is a graph data structure with two kinds of nodes:
+ * - MFFunctionNode: Represents a multi-function. Its input and output sockets correspond to
+ * parameters of the referenced multi-function.
+ * - MFDummyNode: Does not reference a multi-function. Instead it just has sockets that can be
+ * used to represent node group inputs and outputs.
+ *
+ * Links represent data flow. Unlinked input sockets have no value. In order to execute a function
+ * node, all its inputs have to be connected to something.
+ *
+ * Links are only allowed between sockets with the exact same MFDataType. There are no implicit
+ * conversions.
+ *
+ * Every input and output parameter of a multi-function corresponds to exactly one input or output
+ * socket respectively. A multiple parameter belongs to exactly one input AND one output socket.
+ *
+ * There is an .to_dot() method that generates a graph in dot format for debugging purposes.
+ */
+
+#include "FN_multi_function.hh"
+
+#include "BLI_vector_set.hh"
+
+namespace blender {
+namespace fn {
+
+class MFNode;
+class MFFunctionNode;
+class MFDummyNode;
+class MFSocket;
+class MFInputSocket;
+class MFOutputSocket;
+class MFNetwork;
+
+class MFNode : NonCopyable, NonMovable {
+ protected:
+ MFNetwork *m_network;
+ Span<MFInputSocket *> m_inputs;
+ Span<MFOutputSocket *> m_outputs;
+ bool m_is_dummy;
+ uint m_id;
+
+ friend MFNetwork;
+
+ public:
+ StringRefNull name() const;
+
+ uint id() const;
+
+ MFNetwork &network();
+ const MFNetwork &network() const;
+
+ bool is_dummy() const;
+ bool is_function() const;
+
+ MFDummyNode &as_dummy();
+ const MFDummyNode &as_dummy() const;
+
+ MFFunctionNode &as_function();
+ const MFFunctionNode &as_function() const;
+
+ MFInputSocket &input(uint index);
+ const MFInputSocket &input(uint index) const;
+
+ MFOutputSocket &output(uint index);
+ const MFOutputSocket &output(uint index) const;
+
+ Span<MFInputSocket *> inputs();
+ Span<const MFInputSocket *> inputs() const;
+
+ Span<MFOutputSocket *> outputs();
+ Span<const MFOutputSocket *> outputs() const;
+
+ template<typename FuncT> void foreach_origin_socket(const FuncT &func) const;
+
+ bool all_inputs_have_origin() const;
+
+ private:
+ void destruct_sockets();
+};
+
+class MFFunctionNode : public MFNode {
+ private:
+ const MultiFunction *m_function;
+ Span<uint> m_input_param_indices;
+ Span<uint> m_output_param_indices;
+
+ friend MFNetwork;
+
+ public:
+ StringRefNull name() const;
+
+ const MultiFunction &function() const;
+
+ const MFInputSocket &input_for_param(uint param_index) const;
+ const MFOutputSocket &output_for_param(uint param_index) const;
+};
+
+class MFDummyNode : public MFNode {
+ private:
+ StringRefNull m_name;
+ MutableSpan<StringRefNull> m_input_names;
+ MutableSpan<StringRefNull> m_output_names;
+
+ friend MFNetwork;
+
+ public:
+ StringRefNull name() const;
+
+ Span<StringRefNull> input_names() const;
+ Span<StringRefNull> output_names() const;
+};
+
+class MFSocket : NonCopyable, NonMovable {
+ protected:
+ MFNode *m_node;
+ bool m_is_output;
+ uint m_index;
+ MFDataType m_data_type;
+ uint m_id;
+ StringRefNull m_name;
+
+ friend MFNetwork;
+
+ public:
+ StringRefNull name() const;
+
+ uint id() const;
+
+ const MFDataType &data_type() const;
+
+ MFNode &node();
+ const MFNode &node() const;
+
+ bool is_input() const;
+ bool is_output() const;
+
+ MFInputSocket &as_input();
+ const MFInputSocket &as_input() const;
+
+ MFOutputSocket &as_output();
+ const MFOutputSocket &as_output() const;
+};
+
+class MFInputSocket : public MFSocket {
+ private:
+ MFOutputSocket *m_origin;
+
+ friend MFNetwork;
+
+ public:
+ MFOutputSocket *origin();
+ const MFOutputSocket *origin() const;
+};
+
+class MFOutputSocket : public MFSocket {
+ private:
+ Vector<MFInputSocket *, 1> m_targets;
+
+ friend MFNetwork;
+
+ public:
+ Span<MFInputSocket *> targets();
+ Span<const MFInputSocket *> targets() const;
+};
+
+class MFNetwork : NonCopyable, NonMovable {
+ private:
+ LinearAllocator<> m_allocator;
+
+ VectorSet<MFFunctionNode *> m_function_nodes;
+ VectorSet<MFDummyNode *> m_dummy_nodes;
+
+ Vector<MFNode *> m_node_or_null_by_id;
+ Vector<MFSocket *> m_socket_or_null_by_id;
+
+ public:
+ MFNetwork() = default;
+ ~MFNetwork();
+
+ MFFunctionNode &add_function(const MultiFunction &function);
+ MFDummyNode &add_dummy(StringRef name,
+ Span<MFDataType> input_types,
+ Span<MFDataType> output_types,
+ Span<StringRef> input_names,
+ Span<StringRef> output_names);
+ void add_link(MFOutputSocket &from, MFInputSocket &to);
+
+ MFOutputSocket &add_input(StringRef name, MFDataType data_type);
+ MFInputSocket &add_output(StringRef name, MFDataType data_type);
+
+ void relink(MFOutputSocket &old_output, MFOutputSocket &new_output);
+
+ void remove(MFNode &node);
+
+ uint max_socket_id() const;
+
+ std::string to_dot() const;
+};
+
+/* --------------------------------------------------------------------
+ * MFNode inline methods.
+ */
+
+inline StringRefNull MFNode::name() const
+{
+ if (m_is_dummy) {
+ return this->as_dummy().name();
+ }
+ else {
+ return this->as_function().name();
+ }
+}
+
+inline uint MFNode::id() const
+{
+ return m_id;
+}
+
+inline MFNetwork &MFNode::network()
+{
+ return *m_network;
+}
+
+inline const MFNetwork &MFNode::network() const
+{
+ return *m_network;
+}
+
+inline bool MFNode::is_dummy() const
+{
+ return m_is_dummy;
+}
+
+inline bool MFNode::is_function() const
+{
+ return !m_is_dummy;
+}
+
+inline MFDummyNode &MFNode::as_dummy()
+{
+ BLI_assert(m_is_dummy);
+ return *(MFDummyNode *)this;
+}
+
+inline const MFDummyNode &MFNode::as_dummy() const
+{
+ BLI_assert(m_is_dummy);
+ return *(const MFDummyNode *)this;
+}
+
+inline MFFunctionNode &MFNode::as_function()
+{
+ BLI_assert(!m_is_dummy);
+ return *(MFFunctionNode *)this;
+}
+
+inline const MFFunctionNode &MFNode::as_function() const
+{
+ BLI_assert(!m_is_dummy);
+ return *(const MFFunctionNode *)this;
+}
+
+inline MFInputSocket &MFNode::input(uint index)
+{
+ return *m_inputs[index];
+}
+
+inline const MFInputSocket &MFNode::input(uint index) const
+{
+ return *m_inputs[index];
+}
+
+inline MFOutputSocket &MFNode::output(uint index)
+{
+ return *m_outputs[index];
+}
+
+inline const MFOutputSocket &MFNode::output(uint index) const
+{
+ return *m_outputs[index];
+}
+
+inline Span<MFInputSocket *> MFNode::inputs()
+{
+ return m_inputs;
+}
+
+inline Span<const MFInputSocket *> MFNode::inputs() const
+{
+ return m_inputs;
+}
+
+inline Span<MFOutputSocket *> MFNode::outputs()
+{
+ return m_outputs;
+}
+
+inline Span<const MFOutputSocket *> MFNode::outputs() const
+{
+ return m_outputs;
+}
+
+template<typename FuncT> void MFNode::foreach_origin_socket(const FuncT &func) const
+{
+ for (const MFInputSocket *socket : m_inputs) {
+ const MFOutputSocket *origin = socket->origin();
+ if (origin != nullptr) {
+ func(*origin);
+ }
+ }
+}
+
+inline bool MFNode::all_inputs_have_origin() const
+{
+ for (const MFInputSocket *socket : m_inputs) {
+ if (socket->origin() == nullptr) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/* --------------------------------------------------------------------
+ * MFFunctionNode inline methods.
+ */
+
+inline StringRefNull MFFunctionNode::name() const
+{
+ return m_function->name();
+}
+
+inline const MultiFunction &MFFunctionNode::function() const
+{
+ return *m_function;
+}
+
+inline const MFInputSocket &MFFunctionNode::input_for_param(uint param_index) const
+{
+ return this->input(m_input_param_indices.first_index(param_index));
+}
+
+inline const MFOutputSocket &MFFunctionNode::output_for_param(uint param_index) const
+{
+ return this->output(m_output_param_indices.first_index(param_index));
+}
+
+/* --------------------------------------------------------------------
+ * MFDummyNode inline methods.
+ */
+
+inline StringRefNull MFDummyNode::name() const
+{
+ return m_name;
+}
+
+inline Span<StringRefNull> MFDummyNode::input_names() const
+{
+ return m_input_names;
+}
+
+inline Span<StringRefNull> MFDummyNode::output_names() const
+{
+ return m_output_names;
+}
+
+/* --------------------------------------------------------------------
+ * MFSocket inline methods.
+ */
+
+inline StringRefNull MFSocket::name() const
+{
+ return m_name;
+}
+
+inline uint MFSocket::id() const
+{
+ return m_id;
+}
+
+inline const MFDataType &MFSocket::data_type() const
+{
+ return m_data_type;
+}
+
+inline MFNode &MFSocket::node()
+{
+ return *m_node;
+}
+
+inline const MFNode &MFSocket::node() const
+{
+ return *m_node;
+}
+
+inline bool MFSocket::is_input() const
+{
+ return !m_is_output;
+}
+
+inline bool MFSocket::is_output() const
+{
+ return m_is_output;
+}
+
+inline MFInputSocket &MFSocket::as_input()
+{
+ BLI_assert(this->is_input());
+ return *(MFInputSocket *)this;
+}
+
+inline const MFInputSocket &MFSocket::as_input() const
+{
+ BLI_assert(this->is_input());
+ return *(const MFInputSocket *)this;
+}
+
+inline MFOutputSocket &MFSocket::as_output()
+{
+ BLI_assert(this->is_output());
+ return *(MFOutputSocket *)this;
+}
+
+inline const MFOutputSocket &MFSocket::as_output() const
+{
+ BLI_assert(this->is_output());
+ return *(const MFOutputSocket *)this;
+}
+
+/* --------------------------------------------------------------------
+ * MFInputSocket inline methods.
+ */
+
+inline MFOutputSocket *MFInputSocket::origin()
+{
+ return m_origin;
+}
+
+inline const MFOutputSocket *MFInputSocket::origin() const
+{
+ return m_origin;
+}
+
+/* --------------------------------------------------------------------
+ * MFOutputSocket inline methods.
+ */
+
+inline Span<MFInputSocket *> MFOutputSocket::targets()
+{
+ return m_targets;
+}
+
+inline Span<const MFInputSocket *> MFOutputSocket::targets() const
+{
+ return m_targets.as_span();
+}
+
+/* --------------------------------------------------------------------
+ * MFNetwork inline methods.
+ */
+
+inline uint MFNetwork::max_socket_id() const
+{
+ return m_socket_or_null_by_id.size() - 1;
+}
+
+} // namespace fn
+} // namespace blender
+
+#endif /* __FN_MULTI_FUNCTION_NETWORK_HH__ */
diff --git a/source/blender/functions/FN_multi_function_network_evaluation.hh b/source/blender/functions/FN_multi_function_network_evaluation.hh
new file mode 100644
index 00000000000..85ccd1361ef
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_network_evaluation.hh
@@ -0,0 +1,66 @@
+/*
+ * 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_NETWORK_EVALUATION_HH__
+#define __FN_MULTI_FUNCTION_NETWORK_EVALUATION_HH__
+
+/** \file
+ * \ingroup fn
+ */
+
+#include "FN_multi_function_network.hh"
+
+namespace blender {
+namespace fn {
+
+class MFNetworkEvaluationStorage;
+
+class MFNetworkEvaluator : public MultiFunction {
+ private:
+ Vector<const MFOutputSocket *> m_inputs;
+ Vector<const MFInputSocket *> m_outputs;
+
+ public:
+ MFNetworkEvaluator(Vector<const MFOutputSocket *> inputs, Vector<const MFInputSocket *> outputs);
+
+ void call(IndexMask mask, MFParams params, MFContext context) const override;
+
+ private:
+ using Storage = MFNetworkEvaluationStorage;
+
+ void copy_inputs_to_storage(MFParams params, Storage &storage) const;
+ void copy_outputs_to_storage(
+ MFParams params,
+ Storage &storage,
+ Vector<const MFInputSocket *> &outputs_to_initialize_in_the_end) const;
+
+ void evaluate_network_to_compute_outputs(MFContext &global_context, Storage &storage) const;
+
+ void evaluate_function(MFContext &global_context,
+ const MFFunctionNode &function_node,
+ Storage &storage) const;
+
+ bool can_do_single_value_evaluation(const MFFunctionNode &function_node, Storage &storage) const;
+
+ void initialize_remaining_outputs(MFParams params,
+ Storage &storage,
+ Span<const MFInputSocket *> remaining_outputs) const;
+};
+
+} // namespace fn
+} // namespace blender
+
+#endif /* __FN_MULTI_FUNCTION_NETWORK_EVALUATION_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..d89c13505f9
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_param_type.hh
@@ -0,0 +1,165 @@
+/*
+ * 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;
+ }
+
+ bool is_input_or_mutable() const
+ {
+ return ELEM(m_interface_type, Input, Mutable);
+ }
+
+ bool is_output_or_mutable() const
+ {
+ return ELEM(m_interface_type, Output, Mutable);
+ }
+
+ 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..6a0eb698250
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_params.hh
@@ -0,0 +1,240 @@
+/*
+ * 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];
+ }
+
+ template<typename T> GVectorArrayRef<T> vector_mutable(uint param_index, StringRef name = "")
+ {
+ return this->vector_mutable(param_index, name).typed<T>();
+ }
+ 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..77a8ce14c03
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_signature.hh
@@ -0,0 +1,166 @@
+/*
+ * 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;
+ /* Use RawAllocator so that a MultiFunction can have static storage duration. */
+ Vector<std::string, 4, RawAllocator> param_names;
+ Vector<MFParamType, 4, RawAllocator> param_types;
+ Vector<uint, 4, RawAllocator> 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));
+ }
+ template<typename T> void vector_mutable(StringRef name)
+ {
+ this->vector_mutable(name, CPPType::get<T>());
+ }
+ 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..b4607527fb5
--- /dev/null
+++ b/source/blender/functions/FN_spans.hh
@@ -0,0 +1,404 @@
+/*
+ * 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.data(), 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(m_type->is<T>());
+ 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(m_type->is<T>());
+ return MutableSpan<T>((T *)m_buffer, m_size);
+ }
+};
+
+enum class VSpanCategory {
+ Single,
+ FullArray,
+ FullPointerArray,
+};
+
+template<typename T> struct VSpanBase {
+ protected:
+ uint m_virtual_size;
+ VSpanCategory 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:
+ bool is_single_element() const
+ {
+ switch (m_category) {
+ case VSpanCategory::Single:
+ return true;
+ case VSpanCategory::FullArray:
+ return m_virtual_size == 1;
+ case VSpanCategory::FullPointerArray:
+ return m_virtual_size == 1;
+ }
+ BLI_assert(false);
+ return false;
+ }
+
+ bool is_empty() const
+ {
+ return this->m_virtual_size == 0;
+ }
+
+ uint size() const
+ {
+ return this->m_virtual_size;
+ }
+};
+
+BLI_STATIC_ASSERT((sizeof(VSpanBase<void>) == sizeof(VSpanBase<AlignedBuffer<64, 64>>)),
+ "should not depend on the size of the type");
+
+/**
+ * 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 : public VSpanBase<T> {
+ friend class GVSpan;
+
+ VSpan(const VSpanBase<void> &values)
+ {
+ memcpy(this, &values, sizeof(VSpanBase<void>));
+ }
+
+ public:
+ VSpan()
+ {
+ this->m_virtual_size = 0;
+ this->m_category = VSpanCategory::FullArray;
+ this->m_data.full_array.data = nullptr;
+ }
+
+ VSpan(Span<T> values)
+ {
+ this->m_virtual_size = values.size();
+ this->m_category = VSpanCategory::FullArray;
+ this->m_data.full_array.data = values.begin();
+ }
+
+ VSpan(MutableSpan<T> values) : VSpan(Span<T>(values))
+ {
+ }
+
+ VSpan(Span<const T *> values)
+ {
+ this->m_virtual_size = values.size();
+ this->m_category = VSpanCategory::FullPointerArray;
+ this->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 = VSpanCategory::Single;
+ ref.m_data.single.data = value;
+ return ref;
+ }
+
+ const T &operator[](uint index) const
+ {
+ BLI_assert(index < this->m_virtual_size);
+ switch (this->m_category) {
+ case VSpanCategory::Single:
+ return *this->m_data.single.data;
+ case VSpanCategory::FullArray:
+ return this->m_data.full_array.data[index];
+ case VSpanCategory::FullPointerArray:
+ return *this->m_data.full_pointer_array.data[index];
+ }
+ BLI_assert(false);
+ return *this->m_data.single.data;
+ }
+};
+
+/**
+ * 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 : public VSpanBase<void> {
+ private:
+ const CPPType *m_type;
+
+ GVSpan() = default;
+
+ public:
+ GVSpan(const CPPType &type)
+ {
+ this->m_type = &type;
+ this->m_virtual_size = 0;
+ this->m_category = VSpanCategory::FullArray;
+ this->m_data.full_array.data = nullptr;
+ }
+
+ GVSpan(GSpan values)
+ {
+ this->m_type = &values.type();
+ this->m_virtual_size = values.size();
+ this->m_category = VSpanCategory::FullArray;
+ this->m_data.full_array.data = values.buffer();
+ }
+
+ GVSpan(GMutableSpan values) : GVSpan(GSpan(values))
+ {
+ }
+
+ template<typename T> GVSpan(const VSpanBase<T> &values)
+ {
+ this->m_type = &CPPType::get<T>();
+ memcpy(this, &values, sizeof(VSpanBase<void>));
+ }
+
+ 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 = VSpanCategory::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 = VSpanCategory::FullPointerArray;
+ ref.m_data.full_pointer_array.data = values;
+ return ref;
+ }
+
+ const CPPType &type() const
+ {
+ return *this->m_type;
+ }
+
+ const void *operator[](uint index) const
+ {
+ BLI_assert(index < this->m_virtual_size);
+ switch (this->m_category) {
+ case VSpanCategory::Single:
+ return this->m_data.single.data;
+ case VSpanCategory::FullArray:
+ return POINTER_OFFSET(this->m_data.full_array.data, index * m_type->size());
+ case VSpanCategory::FullPointerArray:
+ return this->m_data.full_pointer_array.data[index];
+ }
+ BLI_assert(false);
+ return this->m_data.single.data;
+ }
+
+ template<typename T> VSpan<T> typed() const
+ {
+ BLI_assert(m_type->is<T>());
+ return VSpan<T>(*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(m_virtual_size), dst);
+ }
+
+ void materialize_to_uninitialized(IndexMask mask, void *dst) const
+ {
+ BLI_assert(this->size() >= mask.min_array_size());
+
+ uint element_size = m_type->size();
+ for (uint i : mask) {
+ m_type->copy_to_uninitialized((*this)[i], POINTER_OFFSET(dst, element_size * i));
+ }
+ }
+};
+
+} // namespace fn
+} // namespace blender
+
+#endif /* __FN_SPANS_HH__ */
diff --git a/source/blender/functions/intern/attributes_ref.cc b/source/blender/functions/intern/attributes_ref.cc
new file mode 100644
index 00000000000..dc64f571596
--- /dev/null
+++ b/source/blender/functions/intern/attributes_ref.cc
@@ -0,0 +1,72 @@
+/*
+ * 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_attributes_ref.hh"
+
+namespace blender {
+namespace fn {
+
+AttributesInfoBuilder::~AttributesInfoBuilder()
+{
+ for (uint i : m_defaults.index_range()) {
+ m_types[i]->destruct(m_defaults[i]);
+ }
+}
+
+void AttributesInfoBuilder::add(StringRef name, const CPPType &type, const void *default_value)
+{
+ if (m_names.add_as(name)) {
+ m_types.append(&type);
+
+ if (default_value == nullptr) {
+ default_value = type.default_value();
+ }
+ void *dst = m_allocator.allocate(type.size(), type.alignment());
+ type.copy_to_uninitialized(default_value, dst);
+ m_defaults.append(dst);
+ }
+ else {
+ /* The same name can be added more than once as long as the type is always the same. */
+ BLI_assert(m_types[m_names.index_of_as(name)] == &type);
+ }
+}
+
+AttributesInfo::AttributesInfo(const AttributesInfoBuilder &builder)
+{
+ for (uint i : builder.m_types.index_range()) {
+ StringRefNull name = m_allocator.copy_string(builder.m_names[i]);
+ const CPPType &type = *builder.m_types[i];
+ const void *default_value = builder.m_defaults[i];
+
+ m_index_by_name.add_new(name, i);
+ m_name_by_index.append(name);
+ m_type_by_index.append(&type);
+
+ void *dst = m_allocator.allocate(type.size(), type.alignment());
+ type.copy_to_uninitialized(default_value, dst);
+ m_defaults.append(dst);
+ }
+}
+
+AttributesInfo::~AttributesInfo()
+{
+ for (uint i : m_defaults.index_range()) {
+ m_type_by_index[i]->destruct(m_defaults[i]);
+ }
+}
+
+} // namespace fn
+} // namespace blender
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/source/blender/functions/intern/multi_function.cc b/source/blender/functions/intern/multi_function.cc
new file mode 100644
index 00000000000..8eb5355511d
--- /dev/null
+++ b/source/blender/functions/intern/multi_function.cc
@@ -0,0 +1,40 @@
+/*
+ * 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_multi_function.hh"
+
+namespace blender {
+namespace fn {
+
+class DummyMultiFunction : public MultiFunction {
+ public:
+ DummyMultiFunction()
+ {
+ this->get_builder("Dummy");
+ }
+
+ void call(IndexMask UNUSED(mask),
+ MFParams UNUSED(params),
+ MFContext UNUSED(context)) const override
+ {
+ }
+};
+
+static DummyMultiFunction dummy_multi_function_;
+const MultiFunction &dummy_multi_function = dummy_multi_function_;
+
+} // namespace fn
+} // namespace blender
diff --git a/source/blender/functions/intern/multi_function_network.cc b/source/blender/functions/intern/multi_function_network.cc
new file mode 100644
index 00000000000..93d062f3e5c
--- /dev/null
+++ b/source/blender/functions/intern/multi_function_network.cc
@@ -0,0 +1,278 @@
+/*
+ * 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 "BLI_dot_export.hh"
+#include "FN_multi_function_network.hh"
+
+namespace blender {
+namespace fn {
+
+MFNetwork::~MFNetwork()
+{
+ for (MFFunctionNode *node : m_function_nodes) {
+ node->destruct_sockets();
+ node->~MFFunctionNode();
+ }
+ for (MFDummyNode *node : m_dummy_nodes) {
+ node->destruct_sockets();
+ node->~MFDummyNode();
+ }
+}
+
+void MFNode::destruct_sockets()
+{
+ for (MFInputSocket *socket : m_inputs) {
+ socket->~MFInputSocket();
+ }
+ for (MFOutputSocket *socket : m_outputs) {
+ socket->~MFOutputSocket();
+ }
+}
+
+/**
+ * Add a new function node to the network. The caller keeps the ownership of the function. The
+ * function should not be freed before the network. A reference to the new node is returned. The
+ * node is owned by the network.
+ */
+MFFunctionNode &MFNetwork::add_function(const MultiFunction &function)
+{
+ Vector<uint, 16> input_param_indices, output_param_indices;
+
+ for (uint param_index : function.param_indices()) {
+ switch (function.param_type(param_index).interface_type()) {
+ case MFParamType::Input: {
+ input_param_indices.append(param_index);
+ break;
+ }
+ case MFParamType::Output: {
+ output_param_indices.append(param_index);
+ break;
+ }
+ case MFParamType::Mutable: {
+ input_param_indices.append(param_index);
+ output_param_indices.append(param_index);
+ break;
+ }
+ }
+ }
+
+ MFFunctionNode &node = *m_allocator.construct<MFFunctionNode>();
+ m_function_nodes.add_new(&node);
+
+ node.m_network = this;
+ node.m_is_dummy = false;
+ node.m_id = m_node_or_null_by_id.append_and_get_index(&node);
+ node.m_function = &function;
+ node.m_input_param_indices = m_allocator.construct_array_copy<uint>(input_param_indices);
+ node.m_output_param_indices = m_allocator.construct_array_copy<uint>(output_param_indices);
+
+ node.m_inputs = m_allocator.construct_elements_and_pointer_array<MFInputSocket>(
+ input_param_indices.size());
+ node.m_outputs = m_allocator.construct_elements_and_pointer_array<MFOutputSocket>(
+ output_param_indices.size());
+
+ for (uint i : input_param_indices.index_range()) {
+ uint param_index = input_param_indices[i];
+ MFParamType param = function.param_type(param_index);
+ BLI_assert(param.is_input_or_mutable());
+
+ MFInputSocket &socket = *node.m_inputs[i];
+ socket.m_data_type = param.data_type();
+ socket.m_node = &node;
+ socket.m_index = i;
+ socket.m_is_output = false;
+ socket.m_name = function.param_name(param_index);
+ socket.m_origin = nullptr;
+ socket.m_id = m_socket_or_null_by_id.append_and_get_index(&socket);
+ }
+
+ for (uint i : output_param_indices.index_range()) {
+ uint param_index = output_param_indices[i];
+ MFParamType param = function.param_type(param_index);
+ BLI_assert(param.is_output_or_mutable());
+
+ MFOutputSocket &socket = *node.m_outputs[i];
+ socket.m_data_type = param.data_type();
+ socket.m_node = &node;
+ socket.m_index = i;
+ socket.m_is_output = true;
+ socket.m_name = function.param_name(param_index);
+ socket.m_id = m_socket_or_null_by_id.append_and_get_index(&socket);
+ }
+
+ return node;
+}
+
+/**
+ * Add a dummy node with the given input and output sockets.
+ */
+MFDummyNode &MFNetwork::add_dummy(StringRef name,
+ Span<MFDataType> input_types,
+ Span<MFDataType> output_types,
+ Span<StringRef> input_names,
+ Span<StringRef> output_names)
+{
+ assert_same_size(input_types, input_names);
+ assert_same_size(output_types, output_names);
+
+ MFDummyNode &node = *m_allocator.construct<MFDummyNode>();
+ m_dummy_nodes.add_new(&node);
+
+ node.m_network = this;
+ node.m_is_dummy = true;
+ node.m_name = m_allocator.copy_string(name);
+ node.m_id = m_node_or_null_by_id.append_and_get_index(&node);
+
+ node.m_inputs = m_allocator.construct_elements_and_pointer_array<MFInputSocket>(
+ input_types.size());
+ node.m_outputs = m_allocator.construct_elements_and_pointer_array<MFOutputSocket>(
+ output_types.size());
+
+ node.m_input_names = m_allocator.allocate_array<StringRefNull>(input_types.size());
+ node.m_output_names = m_allocator.allocate_array<StringRefNull>(output_types.size());
+
+ for (uint i : input_types.index_range()) {
+ MFInputSocket &socket = *node.m_inputs[i];
+ socket.m_data_type = input_types[i];
+ socket.m_node = &node;
+ socket.m_index = i;
+ socket.m_is_output = false;
+ socket.m_name = m_allocator.copy_string(input_names[i]);
+ socket.m_id = m_socket_or_null_by_id.append_and_get_index(&socket);
+ node.m_input_names[i] = socket.m_name;
+ }
+
+ for (uint i : output_types.index_range()) {
+ MFOutputSocket &socket = *node.m_outputs[i];
+ socket.m_data_type = output_types[i];
+ socket.m_node = &node;
+ socket.m_index = i;
+ socket.m_is_output = true;
+ socket.m_name = m_allocator.copy_string(output_names[i]);
+ socket.m_id = m_socket_or_null_by_id.append_and_get_index(&socket);
+ node.m_output_names[i] = socket.m_name;
+ }
+
+ return node;
+}
+
+/**
+ * Connect two sockets. This invokes undefined behavior if the sockets belong to different
+ * networks, the sockets have a different data type, or the `to` socket is connected to something
+ * else already.
+ */
+void MFNetwork::add_link(MFOutputSocket &from, MFInputSocket &to)
+{
+ BLI_assert(to.m_origin == nullptr);
+ BLI_assert(from.m_node->m_network == to.m_node->m_network);
+ BLI_assert(from.m_data_type == to.m_data_type);
+ from.m_targets.append(&to);
+ to.m_origin = &from;
+}
+
+MFOutputSocket &MFNetwork::add_input(StringRef name, MFDataType data_type)
+{
+ return this->add_dummy(name, {}, {data_type}, {}, {name}).output(0);
+}
+
+MFInputSocket &MFNetwork::add_output(StringRef name, MFDataType data_type)
+{
+ return this->add_dummy(name, {data_type}, {}, {name}, {}).input(0);
+}
+
+void MFNetwork::relink(MFOutputSocket &old_output, MFOutputSocket &new_output)
+{
+ BLI_assert(&old_output != &new_output);
+ for (MFInputSocket *input : old_output.targets()) {
+ input->m_origin = &new_output;
+ }
+ new_output.m_targets.extend(old_output.m_targets);
+ old_output.m_targets.clear();
+}
+
+void MFNetwork::remove(MFNode &node)
+{
+ for (MFInputSocket *socket : node.m_inputs) {
+ if (socket->m_origin != nullptr) {
+ socket->m_origin->m_targets.remove_first_occurrence_and_reorder(socket);
+ }
+ m_socket_or_null_by_id[socket->m_id] = nullptr;
+ }
+ for (MFOutputSocket *socket : node.m_outputs) {
+ for (MFInputSocket *other : socket->m_targets) {
+ other->m_origin = nullptr;
+ }
+ m_socket_or_null_by_id[socket->m_id] = nullptr;
+ }
+ node.destruct_sockets();
+ if (node.is_dummy()) {
+ MFDummyNode &dummy_node = node.as_dummy();
+ dummy_node.~MFDummyNode();
+ m_dummy_nodes.remove_contained(&dummy_node);
+ }
+ else {
+ MFFunctionNode &function_node = node.as_function();
+ function_node.~MFFunctionNode();
+ m_function_nodes.remove_contained(&function_node);
+ }
+ m_node_or_null_by_id[node.m_id] = nullptr;
+}
+
+std::string MFNetwork::to_dot() const
+{
+ dot::DirectedGraph digraph;
+ digraph.set_rankdir(dot::Attr_rankdir::LeftToRight);
+
+ Map<const MFNode *, dot::NodeWithSocketsRef> dot_nodes;
+
+ Vector<const MFNode *> all_nodes;
+ all_nodes.extend(m_function_nodes.as_span());
+ all_nodes.extend(m_dummy_nodes.as_span());
+
+ for (const MFNode *node : all_nodes) {
+ dot::Node &dot_node = digraph.new_node("");
+
+ Vector<std::string> input_names, output_names;
+ for (const MFInputSocket *socket : node->m_inputs) {
+ input_names.append(socket->name() + "(" + socket->data_type().to_string() + ")");
+ }
+ for (const MFOutputSocket *socket : node->m_outputs) {
+ output_names.append(socket->name() + " (" + socket->data_type().to_string() + ")");
+ }
+
+ dot::NodeWithSocketsRef dot_node_ref{dot_node, node->name(), input_names, output_names};
+ dot_nodes.add_new(node, dot_node_ref);
+ }
+
+ for (const MFNode *to_node : all_nodes) {
+ dot::NodeWithSocketsRef to_dot_node = dot_nodes.lookup(to_node);
+
+ for (const MFInputSocket *to_socket : to_node->m_inputs) {
+ const MFOutputSocket *from_socket = to_socket->m_origin;
+ if (from_socket != nullptr) {
+ const MFNode *from_node = from_socket->m_node;
+ dot::NodeWithSocketsRef from_dot_node = dot_nodes.lookup(from_node);
+ digraph.new_edge(from_dot_node.output(from_socket->m_index),
+ to_dot_node.input(to_socket->m_index));
+ }
+ }
+ }
+
+ return digraph.to_dot_string();
+}
+
+} // namespace fn
+} // namespace blender
diff --git a/source/blender/functions/intern/multi_function_network_evaluation.cc b/source/blender/functions/intern/multi_function_network_evaluation.cc
new file mode 100644
index 00000000000..327a3a66561
--- /dev/null
+++ b/source/blender/functions/intern/multi_function_network_evaluation.cc
@@ -0,0 +1,1063 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup fn
+ *
+ * The `MFNetworkEvaluator` class is a multi-function that consists of potentially many smaller
+ * multi-functions. When called, it traverses the underlying MFNetwork and executes the required
+ * function nodes.
+ *
+ * There are many possible approaches to evaluate a function network. The approach implemented
+ * below has the following features:
+ * - It does not use recursion. Those could become problematic with long node chains.
+ * - It can handle all existing parameter types (including mutable parameters).
+ * - Avoids data copies in many cases.
+ * - Every node is executed at most once.
+ * - Can compute sub-functions on a single element, when the result is the same for all elements.
+ *
+ * Possible improvements:
+ * - Cache and reuse buffers.
+ * - Use "deepest depth first" heuristic to decide which order the inputs of a node should be
+ * computed. This reduces the number of required temporary buffers when they are reused.
+ */
+
+#include "FN_multi_function_network_evaluation.hh"
+
+#include "BLI_stack.hh"
+
+namespace blender {
+namespace fn {
+
+struct Value;
+
+/**
+ * This keeps track of all the values that flow through the multi-function network. Therefore it
+ * maintains a mapping between output sockets and their corresponding values. Every `value`
+ * references some memory, that is owned either by the caller or this storage.
+ *
+ * A value can be owned by different sockets over time to avoid unnecessary copies.
+ */
+class MFNetworkEvaluationStorage {
+ private:
+ LinearAllocator<> m_allocator;
+ IndexMask m_mask;
+ Array<Value *> m_value_per_output_id;
+ uint m_min_array_size;
+
+ public:
+ MFNetworkEvaluationStorage(IndexMask mask, uint max_socket_id);
+ ~MFNetworkEvaluationStorage();
+
+ /* Add the values that have been provided by the caller of the multi-function network. */
+ void add_single_input_from_caller(const MFOutputSocket &socket, GVSpan virtual_span);
+ void add_vector_input_from_caller(const MFOutputSocket &socket, GVArraySpan virtual_array_span);
+ void add_single_output_from_caller(const MFOutputSocket &socket, GMutableSpan span);
+ void add_vector_output_from_caller(const MFOutputSocket &socket, GVectorArray &vector_array);
+
+ /* Get input buffers for function node evaluations. */
+ GVSpan get_single_input__full(const MFInputSocket &socket);
+ GVSpan get_single_input__single(const MFInputSocket &socket);
+ GVArraySpan get_vector_input__full(const MFInputSocket &socket);
+ GVArraySpan get_vector_input__single(const MFInputSocket &socket);
+
+ /* Get output buffers for function node evaluations. */
+ GMutableSpan get_single_output__full(const MFOutputSocket &socket);
+ GMutableSpan get_single_output__single(const MFOutputSocket &socket);
+ GVectorArray &get_vector_output__full(const MFOutputSocket &socket);
+ GVectorArray &get_vector_output__single(const MFOutputSocket &socket);
+
+ /* Get mutable buffers for function node evaluations. */
+ GMutableSpan get_mutable_single__full(const MFInputSocket &input, const MFOutputSocket &output);
+ GMutableSpan get_mutable_single__single(const MFInputSocket &input,
+ const MFOutputSocket &output);
+ GVectorArray &get_mutable_vector__full(const MFInputSocket &input, const MFOutputSocket &output);
+ GVectorArray &get_mutable_vector__single(const MFInputSocket &input,
+ const MFOutputSocket &output);
+
+ /* Mark a node as being done with evaluation. This might free temporary buffers that are no
+ * longer needed. */
+ void finish_node(const MFFunctionNode &node);
+ void finish_output_socket(const MFOutputSocket &socket);
+ void finish_input_socket(const MFInputSocket &socket);
+
+ IndexMask mask() const;
+ bool socket_is_computed(const MFOutputSocket &socket);
+ bool is_same_value_for_every_index(const MFOutputSocket &socket);
+ bool socket_has_buffer_for_output(const MFOutputSocket &socket);
+};
+
+MFNetworkEvaluator::MFNetworkEvaluator(Vector<const MFOutputSocket *> inputs,
+ Vector<const MFInputSocket *> outputs)
+ : m_inputs(std::move(inputs)), m_outputs(std::move(outputs))
+{
+ BLI_assert(m_outputs.size() > 0);
+ MFSignatureBuilder signature = this->get_builder("Function Tree");
+
+ for (auto socket : m_inputs) {
+ BLI_assert(socket->node().is_dummy());
+
+ MFDataType type = socket->data_type();
+ switch (type.category()) {
+ case MFDataType::Single:
+ signature.single_input("Input", type.single_type());
+ break;
+ case MFDataType::Vector:
+ signature.vector_input("Input", type.vector_base_type());
+ break;
+ }
+ }
+
+ for (auto socket : m_outputs) {
+ BLI_assert(socket->node().is_dummy());
+
+ MFDataType type = socket->data_type();
+ switch (type.category()) {
+ case MFDataType::Single:
+ signature.single_output("Output", type.single_type());
+ break;
+ case MFDataType::Vector:
+ signature.vector_output("Output", type.vector_base_type());
+ break;
+ }
+ }
+}
+
+void MFNetworkEvaluator::call(IndexMask mask, MFParams params, MFContext context) const
+{
+ if (mask.size() == 0) {
+ return;
+ }
+
+ const MFNetwork &network = m_outputs[0]->node().network();
+ Storage storage(mask, network.max_socket_id());
+
+ Vector<const MFInputSocket *> outputs_to_initialize_in_the_end;
+
+ this->copy_inputs_to_storage(params, storage);
+ this->copy_outputs_to_storage(params, storage, outputs_to_initialize_in_the_end);
+ this->evaluate_network_to_compute_outputs(context, storage);
+ this->initialize_remaining_outputs(params, storage, outputs_to_initialize_in_the_end);
+}
+
+BLI_NOINLINE void MFNetworkEvaluator::copy_inputs_to_storage(MFParams params,
+ Storage &storage) const
+{
+ for (uint input_index : m_inputs.index_range()) {
+ uint param_index = input_index + 0;
+ const MFOutputSocket &socket = *m_inputs[input_index];
+ switch (socket.data_type().category()) {
+ case MFDataType::Single: {
+ GVSpan input_list = params.readonly_single_input(param_index);
+ storage.add_single_input_from_caller(socket, input_list);
+ break;
+ }
+ case MFDataType::Vector: {
+ GVArraySpan input_list_list = params.readonly_vector_input(param_index);
+ storage.add_vector_input_from_caller(socket, input_list_list);
+ break;
+ }
+ }
+ }
+}
+
+BLI_NOINLINE void MFNetworkEvaluator::copy_outputs_to_storage(
+ MFParams params,
+ Storage &storage,
+ Vector<const MFInputSocket *> &outputs_to_initialize_in_the_end) const
+{
+ for (uint output_index : m_outputs.index_range()) {
+ uint param_index = output_index + m_inputs.size();
+ const MFInputSocket &socket = *m_outputs[output_index];
+ const MFOutputSocket &origin = *socket.origin();
+
+ if (origin.node().is_dummy()) {
+ BLI_assert(m_inputs.contains(&origin));
+ /* Don't overwrite input buffers. */
+ outputs_to_initialize_in_the_end.append(&socket);
+ continue;
+ }
+
+ if (storage.socket_has_buffer_for_output(origin)) {
+ /* When two outputs will be initialized to the same values. */
+ outputs_to_initialize_in_the_end.append(&socket);
+ continue;
+ }
+
+ switch (socket.data_type().category()) {
+ case MFDataType::Single: {
+ GMutableSpan span = params.uninitialized_single_output(param_index);
+ storage.add_single_output_from_caller(origin, span);
+ break;
+ }
+ case MFDataType::Vector: {
+ GVectorArray &vector_array = params.vector_output(param_index);
+ storage.add_vector_output_from_caller(origin, vector_array);
+ break;
+ }
+ }
+ }
+}
+
+BLI_NOINLINE void MFNetworkEvaluator::evaluate_network_to_compute_outputs(
+ MFContext &global_context, Storage &storage) const
+{
+ Stack<const MFOutputSocket *, 32> sockets_to_compute;
+ for (const MFInputSocket *socket : m_outputs) {
+ sockets_to_compute.push(socket->origin());
+ }
+
+ Vector<const MFOutputSocket *, 32> missing_sockets;
+
+ /* This is the main loop that traverses the MFNetwork. */
+ while (!sockets_to_compute.is_empty()) {
+ const MFOutputSocket &socket = *sockets_to_compute.peek();
+ const MFNode &node = socket.node();
+
+ if (storage.socket_is_computed(socket)) {
+ sockets_to_compute.pop();
+ continue;
+ }
+
+ BLI_assert(node.is_function());
+ BLI_assert(node.all_inputs_have_origin());
+ const MFFunctionNode &function_node = node.as_function();
+
+ missing_sockets.clear();
+ function_node.foreach_origin_socket([&](const MFOutputSocket &origin) {
+ if (!storage.socket_is_computed(origin)) {
+ missing_sockets.append(&origin);
+ }
+ });
+
+ sockets_to_compute.push_multiple(missing_sockets);
+
+ bool all_inputs_are_computed = missing_sockets.size() == 0;
+ if (all_inputs_are_computed) {
+ this->evaluate_function(global_context, function_node, storage);
+ sockets_to_compute.pop();
+ }
+ }
+}
+
+BLI_NOINLINE void MFNetworkEvaluator::evaluate_function(MFContext &global_context,
+ const MFFunctionNode &function_node,
+ Storage &storage) const
+{
+ const MultiFunction &function = function_node.function();
+ // std::cout << "Function: " << function.name() << "\n";
+
+ if (this->can_do_single_value_evaluation(function_node, storage)) {
+ /* The function output would be the same for all elements. Therefore, it is enough to call the
+ * function only on a single element. This can avoid many duplicate computations. */
+ MFParamsBuilder params{function, 1};
+
+ for (uint param_index : function.param_indices()) {
+ MFParamType param_type = function.param_type(param_index);
+ switch (param_type.category()) {
+ case MFParamType::SingleInput: {
+ const MFInputSocket &socket = function_node.input_for_param(param_index);
+ GVSpan values = storage.get_single_input__single(socket);
+ params.add_readonly_single_input(values);
+ break;
+ }
+ case MFParamType::VectorInput: {
+ const MFInputSocket &socket = function_node.input_for_param(param_index);
+ GVArraySpan values = storage.get_vector_input__single(socket);
+ params.add_readonly_vector_input(values);
+ break;
+ }
+ case MFParamType::SingleOutput: {
+ const MFOutputSocket &socket = function_node.output_for_param(param_index);
+ GMutableSpan values = storage.get_single_output__single(socket);
+ params.add_uninitialized_single_output(values);
+ break;
+ }
+ case MFParamType::VectorOutput: {
+ const MFOutputSocket &socket = function_node.output_for_param(param_index);
+ GVectorArray &values = storage.get_vector_output__single(socket);
+ params.add_vector_output(values);
+ break;
+ }
+ case MFParamType::SingleMutable: {
+ const MFInputSocket &input = function_node.input_for_param(param_index);
+ const MFOutputSocket &output = function_node.output_for_param(param_index);
+ GMutableSpan values = storage.get_mutable_single__single(input, output);
+ params.add_single_mutable(values);
+ break;
+ }
+ case MFParamType::VectorMutable: {
+ const MFInputSocket &input = function_node.input_for_param(param_index);
+ const MFOutputSocket &output = function_node.output_for_param(param_index);
+ GVectorArray &values = storage.get_mutable_vector__single(input, output);
+ params.add_vector_mutable(values);
+ break;
+ }
+ }
+ }
+
+ function.call(IndexRange(1), params, global_context);
+ }
+ else {
+ MFParamsBuilder params{function, storage.mask().min_array_size()};
+
+ for (uint param_index : function.param_indices()) {
+ MFParamType param_type = function.param_type(param_index);
+ switch (param_type.category()) {
+ case MFParamType::SingleInput: {
+ const MFInputSocket &socket = function_node.input_for_param(param_index);
+ GVSpan values = storage.get_single_input__full(socket);
+ params.add_readonly_single_input(values);
+ break;
+ }
+ case MFParamType::VectorInput: {
+ const MFInputSocket &socket = function_node.input_for_param(param_index);
+ GVArraySpan values = storage.get_vector_input__full(socket);
+ params.add_readonly_vector_input(values);
+ break;
+ }
+ case MFParamType::SingleOutput: {
+ const MFOutputSocket &socket = function_node.output_for_param(param_index);
+ GMutableSpan values = storage.get_single_output__full(socket);
+ params.add_uninitialized_single_output(values);
+ break;
+ }
+ case MFParamType::VectorOutput: {
+ const MFOutputSocket &socket = function_node.output_for_param(param_index);
+ GVectorArray &values = storage.get_vector_output__full(socket);
+ params.add_vector_output(values);
+ break;
+ }
+ case MFParamType::SingleMutable: {
+ const MFInputSocket &input = function_node.input_for_param(param_index);
+ const MFOutputSocket &output = function_node.output_for_param(param_index);
+ GMutableSpan values = storage.get_mutable_single__full(input, output);
+ params.add_single_mutable(values);
+ break;
+ }
+ case MFParamType::VectorMutable: {
+ const MFInputSocket &input = function_node.input_for_param(param_index);
+ const MFOutputSocket &output = function_node.output_for_param(param_index);
+ GVectorArray &values = storage.get_mutable_vector__full(input, output);
+ params.add_vector_mutable(values);
+ break;
+ }
+ }
+ }
+
+ function.call(storage.mask(), params, global_context);
+ }
+
+ storage.finish_node(function_node);
+}
+
+bool MFNetworkEvaluator::can_do_single_value_evaluation(const MFFunctionNode &function_node,
+ Storage &storage) const
+{
+ for (const MFInputSocket *socket : function_node.inputs()) {
+ if (!storage.is_same_value_for_every_index(*socket->origin())) {
+ return false;
+ }
+ }
+ if (storage.mask().min_array_size() >= 1) {
+ for (const MFOutputSocket *socket : function_node.outputs()) {
+ if (storage.socket_has_buffer_for_output(*socket)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+BLI_NOINLINE void MFNetworkEvaluator::initialize_remaining_outputs(
+ MFParams params, Storage &storage, Span<const MFInputSocket *> remaining_outputs) const
+{
+ for (const MFInputSocket *socket : remaining_outputs) {
+ uint param_index = m_inputs.size() + m_outputs.first_index_of(socket);
+
+ switch (socket->data_type().category()) {
+ case MFDataType::Single: {
+ GVSpan values = storage.get_single_input__full(*socket);
+ GMutableSpan output_values = params.uninitialized_single_output(param_index);
+ values.materialize_to_uninitialized(storage.mask(), output_values.buffer());
+ break;
+ }
+ case MFDataType::Vector: {
+ GVArraySpan values = storage.get_vector_input__full(*socket);
+ GVectorArray &output_values = params.vector_output(param_index);
+ output_values.extend(storage.mask(), values);
+ break;
+ }
+ }
+ }
+}
+
+/* -------------------------------------------------------------------- */
+/** \name Value Types
+ * \{ */
+
+enum class ValueType {
+ InputSingle,
+ InputVector,
+ OutputSingle,
+ OutputVector,
+ OwnSingle,
+ OwnVector,
+};
+
+struct Value {
+ ValueType type;
+
+ Value(ValueType type) : type(type)
+ {
+ }
+};
+
+struct InputSingleValue : public Value {
+ /** This span has been provided by the code that called the multi-function network. */
+ GVSpan virtual_span;
+
+ InputSingleValue(GVSpan virtual_span) : Value(ValueType::InputSingle), virtual_span(virtual_span)
+ {
+ }
+};
+
+struct InputVectorValue : public Value {
+ /** This span has been provided by the code that called the multi-function network. */
+ GVArraySpan virtual_array_span;
+
+ InputVectorValue(GVArraySpan virtual_array_span)
+ : Value(ValueType::InputVector), virtual_array_span(virtual_array_span)
+ {
+ }
+};
+
+struct OutputValue : public Value {
+ bool is_computed = false;
+
+ OutputValue(ValueType type) : Value(type)
+ {
+ }
+};
+
+struct OutputSingleValue : public OutputValue {
+ /** This span has been provided by the code that called the multi-function network. */
+ GMutableSpan span;
+
+ OutputSingleValue(GMutableSpan span) : OutputValue(ValueType::OutputSingle), span(span)
+ {
+ }
+};
+
+struct OutputVectorValue : public OutputValue {
+ /** This vector array has been provided by the code that called the multi-function network. */
+ GVectorArray *vector_array;
+
+ OutputVectorValue(GVectorArray &vector_array)
+ : OutputValue(ValueType::OutputVector), vector_array(&vector_array)
+ {
+ }
+};
+
+struct OwnSingleValue : public Value {
+ /** This span has been allocated during the evaluation of the multi-function network and contains
+ * intermediate data. It has to be freed once the network evaluation is finished. */
+ GMutableSpan span;
+ int max_remaining_users;
+ bool is_single_allocated;
+
+ OwnSingleValue(GMutableSpan span, int max_remaining_users, bool is_single_allocated)
+ : Value(ValueType::OwnSingle),
+ span(span),
+ max_remaining_users(max_remaining_users),
+ is_single_allocated(is_single_allocated)
+ {
+ }
+};
+
+struct OwnVectorValue : public Value {
+ /** This vector array has been allocated during the evaluation of the multi-function network and
+ * contains intermediate data. It has to be freed once the network evaluation is finished. */
+ GVectorArray *vector_array;
+ int max_remaining_users;
+
+ OwnVectorValue(GVectorArray &vector_array, int max_remaining_users)
+ : Value(ValueType::OwnVector),
+ vector_array(&vector_array),
+ max_remaining_users(max_remaining_users)
+ {
+ }
+};
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Storage methods
+ * \{ */
+
+MFNetworkEvaluationStorage::MFNetworkEvaluationStorage(IndexMask mask, uint max_socket_id)
+ : m_mask(mask),
+ m_value_per_output_id(max_socket_id + 1, nullptr),
+ m_min_array_size(mask.min_array_size())
+{
+}
+
+MFNetworkEvaluationStorage::~MFNetworkEvaluationStorage()
+{
+ for (Value *any_value : m_value_per_output_id) {
+ if (any_value == nullptr) {
+ continue;
+ }
+ else if (any_value->type == ValueType::OwnSingle) {
+ OwnSingleValue *value = (OwnSingleValue *)any_value;
+ GMutableSpan span = value->span;
+ const CPPType &type = span.type();
+ if (value->is_single_allocated) {
+ type.destruct(span.buffer());
+ }
+ else {
+ type.destruct_indices(span.buffer(), m_mask);
+ MEM_freeN(span.buffer());
+ }
+ }
+ else if (any_value->type == ValueType::OwnVector) {
+ OwnVectorValue *value = (OwnVectorValue *)any_value;
+ delete value->vector_array;
+ }
+ }
+}
+
+IndexMask MFNetworkEvaluationStorage::mask() const
+{
+ return m_mask;
+}
+
+bool MFNetworkEvaluationStorage::socket_is_computed(const MFOutputSocket &socket)
+{
+ Value *any_value = m_value_per_output_id[socket.id()];
+ if (any_value == nullptr) {
+ return false;
+ }
+ if (ELEM(any_value->type, ValueType::OutputSingle, ValueType::OutputVector)) {
+ return ((OutputValue *)any_value)->is_computed;
+ }
+ return true;
+}
+
+bool MFNetworkEvaluationStorage::is_same_value_for_every_index(const MFOutputSocket &socket)
+{
+ Value *any_value = m_value_per_output_id[socket.id()];
+ switch (any_value->type) {
+ case ValueType::OwnSingle:
+ return ((OwnSingleValue *)any_value)->span.size() == 1;
+ case ValueType::OwnVector:
+ return ((OwnVectorValue *)any_value)->vector_array->size() == 1;
+ case ValueType::InputSingle:
+ return ((InputSingleValue *)any_value)->virtual_span.is_single_element();
+ case ValueType::InputVector:
+ return ((InputVectorValue *)any_value)->virtual_array_span.is_single_array();
+ case ValueType::OutputSingle:
+ return ((OutputSingleValue *)any_value)->span.size() == 1;
+ case ValueType::OutputVector:
+ return ((OutputVectorValue *)any_value)->vector_array->size() == 1;
+ }
+ BLI_assert(false);
+ return false;
+}
+
+bool MFNetworkEvaluationStorage::socket_has_buffer_for_output(const MFOutputSocket &socket)
+{
+ Value *any_value = m_value_per_output_id[socket.id()];
+ if (any_value == nullptr) {
+ return false;
+ }
+
+ BLI_assert(ELEM(any_value->type, ValueType::OutputSingle, ValueType::OutputVector));
+ return true;
+}
+
+void MFNetworkEvaluationStorage::finish_node(const MFFunctionNode &node)
+{
+ for (const MFInputSocket *socket : node.inputs()) {
+ this->finish_input_socket(*socket);
+ }
+ for (const MFOutputSocket *socket : node.outputs()) {
+ this->finish_output_socket(*socket);
+ }
+}
+
+void MFNetworkEvaluationStorage::finish_output_socket(const MFOutputSocket &socket)
+{
+ Value *any_value = m_value_per_output_id[socket.id()];
+ if (any_value == nullptr) {
+ return;
+ }
+
+ if (ELEM(any_value->type, ValueType::OutputSingle, ValueType::OutputVector)) {
+ ((OutputValue *)any_value)->is_computed = true;
+ }
+}
+
+void MFNetworkEvaluationStorage::finish_input_socket(const MFInputSocket &socket)
+{
+ const MFOutputSocket &origin = *socket.origin();
+
+ Value *any_value = m_value_per_output_id[origin.id()];
+ if (any_value == nullptr) {
+ /* Can happen when a value has been forward to the next node. */
+ return;
+ }
+
+ switch (any_value->type) {
+ case ValueType::InputSingle:
+ case ValueType::OutputSingle:
+ case ValueType::InputVector:
+ case ValueType::OutputVector: {
+ break;
+ }
+ case ValueType::OwnSingle: {
+ OwnSingleValue *value = (OwnSingleValue *)any_value;
+ BLI_assert(value->max_remaining_users >= 1);
+ value->max_remaining_users--;
+ if (value->max_remaining_users == 0) {
+ GMutableSpan span = value->span;
+ const CPPType &type = span.type();
+ if (value->is_single_allocated) {
+ type.destruct(span.buffer());
+ }
+ else {
+ type.destruct_indices(span.buffer(), m_mask);
+ MEM_freeN(span.buffer());
+ }
+ m_value_per_output_id[origin.id()] = nullptr;
+ }
+ break;
+ }
+ case ValueType::OwnVector: {
+ OwnVectorValue *value = (OwnVectorValue *)any_value;
+ BLI_assert(value->max_remaining_users >= 1);
+ value->max_remaining_users--;
+ if (value->max_remaining_users == 0) {
+ delete value->vector_array;
+ m_value_per_output_id[origin.id()] = nullptr;
+ }
+ break;
+ }
+ }
+}
+
+void MFNetworkEvaluationStorage::add_single_input_from_caller(const MFOutputSocket &socket,
+ GVSpan virtual_span)
+{
+ BLI_assert(m_value_per_output_id[socket.id()] == nullptr);
+ BLI_assert(virtual_span.size() >= m_min_array_size);
+
+ auto *value = m_allocator.construct<InputSingleValue>(virtual_span);
+ m_value_per_output_id[socket.id()] = value;
+}
+
+void MFNetworkEvaluationStorage::add_vector_input_from_caller(const MFOutputSocket &socket,
+ GVArraySpan virtual_array_span)
+{
+ BLI_assert(m_value_per_output_id[socket.id()] == nullptr);
+ BLI_assert(virtual_array_span.size() >= m_min_array_size);
+
+ auto *value = m_allocator.construct<InputVectorValue>(virtual_array_span);
+ m_value_per_output_id[socket.id()] = value;
+}
+
+void MFNetworkEvaluationStorage::add_single_output_from_caller(const MFOutputSocket &socket,
+ GMutableSpan span)
+{
+ BLI_assert(m_value_per_output_id[socket.id()] == nullptr);
+ BLI_assert(span.size() >= m_min_array_size);
+
+ auto *value = m_allocator.construct<OutputSingleValue>(span);
+ m_value_per_output_id[socket.id()] = value;
+}
+
+void MFNetworkEvaluationStorage::add_vector_output_from_caller(const MFOutputSocket &socket,
+ GVectorArray &vector_array)
+{
+ BLI_assert(m_value_per_output_id[socket.id()] == nullptr);
+ BLI_assert(vector_array.size() >= m_min_array_size);
+
+ auto *value = m_allocator.construct<OutputVectorValue>(vector_array);
+ m_value_per_output_id[socket.id()] = value;
+}
+
+GMutableSpan MFNetworkEvaluationStorage::get_single_output__full(const MFOutputSocket &socket)
+{
+ Value *any_value = m_value_per_output_id[socket.id()];
+ if (any_value == nullptr) {
+ const CPPType &type = socket.data_type().single_type();
+ void *buffer = MEM_mallocN_aligned(m_min_array_size * type.size(), type.alignment(), AT);
+ GMutableSpan span(type, buffer, m_min_array_size);
+
+ auto *value = m_allocator.construct<OwnSingleValue>(span, socket.targets().size(), false);
+ m_value_per_output_id[socket.id()] = value;
+
+ return span;
+ }
+ else {
+ BLI_assert(any_value->type == ValueType::OutputSingle);
+ return ((OutputSingleValue *)any_value)->span;
+ }
+}
+
+GMutableSpan MFNetworkEvaluationStorage::get_single_output__single(const MFOutputSocket &socket)
+{
+ Value *any_value = m_value_per_output_id[socket.id()];
+ if (any_value == nullptr) {
+ const CPPType &type = socket.data_type().single_type();
+ void *buffer = m_allocator.allocate(type.size(), type.alignment());
+ GMutableSpan span(type, buffer, 1);
+
+ auto *value = m_allocator.construct<OwnSingleValue>(span, socket.targets().size(), true);
+ m_value_per_output_id[socket.id()] = value;
+
+ return value->span;
+ }
+ else {
+ BLI_assert(any_value->type == ValueType::OutputSingle);
+ GMutableSpan span = ((OutputSingleValue *)any_value)->span;
+ BLI_assert(span.size() == 1);
+ return span;
+ }
+}
+
+GVectorArray &MFNetworkEvaluationStorage::get_vector_output__full(const MFOutputSocket &socket)
+{
+ Value *any_value = m_value_per_output_id[socket.id()];
+ if (any_value == nullptr) {
+ const CPPType &type = socket.data_type().vector_base_type();
+ GVectorArray *vector_array = new GVectorArray(type, m_min_array_size);
+
+ auto *value = m_allocator.construct<OwnVectorValue>(*vector_array, socket.targets().size());
+ m_value_per_output_id[socket.id()] = value;
+
+ return *value->vector_array;
+ }
+ else {
+ BLI_assert(any_value->type == ValueType::OutputVector);
+ return *((OutputVectorValue *)any_value)->vector_array;
+ }
+}
+
+GVectorArray &MFNetworkEvaluationStorage::get_vector_output__single(const MFOutputSocket &socket)
+{
+ Value *any_value = m_value_per_output_id[socket.id()];
+ if (any_value == nullptr) {
+ const CPPType &type = socket.data_type().vector_base_type();
+ GVectorArray *vector_array = new GVectorArray(type, 1);
+
+ auto *value = m_allocator.construct<OwnVectorValue>(*vector_array, socket.targets().size());
+ m_value_per_output_id[socket.id()] = value;
+
+ return *value->vector_array;
+ }
+ else {
+ BLI_assert(any_value->type == ValueType::OutputVector);
+ GVectorArray &vector_array = *((OutputVectorValue *)any_value)->vector_array;
+ BLI_assert(vector_array.size() == 1);
+ return vector_array;
+ }
+}
+
+GMutableSpan MFNetworkEvaluationStorage::get_mutable_single__full(const MFInputSocket &input,
+ const MFOutputSocket &output)
+{
+ const MFOutputSocket &from = *input.origin();
+ const MFOutputSocket &to = output;
+ const CPPType &type = from.data_type().single_type();
+
+ Value *from_any_value = m_value_per_output_id[from.id()];
+ Value *to_any_value = m_value_per_output_id[to.id()];
+ BLI_assert(from_any_value != nullptr);
+ BLI_assert(type == to.data_type().single_type());
+
+ if (to_any_value != nullptr) {
+ BLI_assert(to_any_value->type == ValueType::OutputSingle);
+ GMutableSpan span = ((OutputSingleValue *)to_any_value)->span;
+ GVSpan virtual_span = this->get_single_input__full(input);
+ virtual_span.materialize_to_uninitialized(m_mask, span.buffer());
+ return span;
+ }
+
+ if (from_any_value->type == ValueType::OwnSingle) {
+ OwnSingleValue *value = (OwnSingleValue *)from_any_value;
+ if (value->max_remaining_users == 1 && !value->is_single_allocated) {
+ m_value_per_output_id[to.id()] = value;
+ m_value_per_output_id[from.id()] = nullptr;
+ value->max_remaining_users = to.targets().size();
+ return value->span;
+ }
+ }
+
+ GVSpan virtual_span = this->get_single_input__full(input);
+ void *new_buffer = MEM_mallocN_aligned(m_min_array_size * type.size(), type.alignment(), AT);
+ GMutableSpan new_array_ref(type, new_buffer, m_min_array_size);
+ virtual_span.materialize_to_uninitialized(m_mask, new_array_ref.buffer());
+
+ OwnSingleValue *new_value = m_allocator.construct<OwnSingleValue>(
+ new_array_ref, to.targets().size(), false);
+ m_value_per_output_id[to.id()] = new_value;
+ return new_array_ref;
+}
+
+GMutableSpan MFNetworkEvaluationStorage::get_mutable_single__single(const MFInputSocket &input,
+ const MFOutputSocket &output)
+{
+ const MFOutputSocket &from = *input.origin();
+ const MFOutputSocket &to = output;
+ const CPPType &type = from.data_type().single_type();
+
+ Value *from_any_value = m_value_per_output_id[from.id()];
+ Value *to_any_value = m_value_per_output_id[to.id()];
+ BLI_assert(from_any_value != nullptr);
+ BLI_assert(type == to.data_type().single_type());
+
+ if (to_any_value != nullptr) {
+ BLI_assert(to_any_value->type == ValueType::OutputSingle);
+ GMutableSpan span = ((OutputSingleValue *)to_any_value)->span;
+ BLI_assert(span.size() == 1);
+ GVSpan virtual_span = this->get_single_input__single(input);
+ type.copy_to_uninitialized(virtual_span.as_single_element(), span[0]);
+ return span;
+ }
+
+ if (from_any_value->type == ValueType::OwnSingle) {
+ OwnSingleValue *value = (OwnSingleValue *)from_any_value;
+ if (value->max_remaining_users == 1) {
+ m_value_per_output_id[to.id()] = value;
+ m_value_per_output_id[from.id()] = nullptr;
+ value->max_remaining_users = to.targets().size();
+ BLI_assert(value->span.size() == 1);
+ return value->span;
+ }
+ }
+
+ GVSpan virtual_span = this->get_single_input__single(input);
+
+ void *new_buffer = m_allocator.allocate(type.size(), type.alignment());
+ type.copy_to_uninitialized(virtual_span.as_single_element(), new_buffer);
+ GMutableSpan new_array_ref(type, new_buffer, 1);
+
+ OwnSingleValue *new_value = m_allocator.construct<OwnSingleValue>(
+ new_array_ref, to.targets().size(), true);
+ m_value_per_output_id[to.id()] = new_value;
+ return new_array_ref;
+}
+
+GVectorArray &MFNetworkEvaluationStorage::get_mutable_vector__full(const MFInputSocket &input,
+ const MFOutputSocket &output)
+{
+ const MFOutputSocket &from = *input.origin();
+ const MFOutputSocket &to = output;
+ const CPPType &base_type = from.data_type().vector_base_type();
+
+ Value *from_any_value = m_value_per_output_id[from.id()];
+ Value *to_any_value = m_value_per_output_id[to.id()];
+ BLI_assert(from_any_value != nullptr);
+ BLI_assert(base_type == to.data_type().vector_base_type());
+
+ if (to_any_value != nullptr) {
+ BLI_assert(to_any_value->type == ValueType::OutputVector);
+ GVectorArray &vector_array = *((OutputVectorValue *)to_any_value)->vector_array;
+ GVArraySpan virtual_array_span = this->get_vector_input__full(input);
+ vector_array.extend(m_mask, virtual_array_span);
+ return vector_array;
+ }
+
+ if (from_any_value->type == ValueType::OwnVector) {
+ OwnVectorValue *value = (OwnVectorValue *)from_any_value;
+ if (value->max_remaining_users == 1) {
+ m_value_per_output_id[to.id()] = value;
+ m_value_per_output_id[from.id()] = nullptr;
+ value->max_remaining_users = to.targets().size();
+ return *value->vector_array;
+ }
+ }
+
+ GVArraySpan virtual_array_span = this->get_vector_input__full(input);
+
+ GVectorArray *new_vector_array = new GVectorArray(base_type, m_min_array_size);
+ new_vector_array->extend(m_mask, virtual_array_span);
+
+ OwnVectorValue *new_value = m_allocator.construct<OwnVectorValue>(*new_vector_array,
+ to.targets().size());
+ m_value_per_output_id[to.id()] = new_value;
+
+ return *new_vector_array;
+}
+
+GVectorArray &MFNetworkEvaluationStorage::get_mutable_vector__single(const MFInputSocket &input,
+ const MFOutputSocket &output)
+{
+ const MFOutputSocket &from = *input.origin();
+ const MFOutputSocket &to = output;
+ const CPPType &base_type = from.data_type().vector_base_type();
+
+ Value *from_any_value = m_value_per_output_id[from.id()];
+ Value *to_any_value = m_value_per_output_id[to.id()];
+ BLI_assert(from_any_value != nullptr);
+ BLI_assert(base_type == to.data_type().vector_base_type());
+
+ if (to_any_value != nullptr) {
+ BLI_assert(to_any_value->type == ValueType::OutputVector);
+ GVectorArray &vector_array = *((OutputVectorValue *)to_any_value)->vector_array;
+ BLI_assert(vector_array.size() == 1);
+ GVArraySpan virtual_array_span = this->get_vector_input__single(input);
+ vector_array.extend(0, virtual_array_span[0]);
+ return vector_array;
+ }
+
+ if (from_any_value->type == ValueType::OwnVector) {
+ OwnVectorValue *value = (OwnVectorValue *)from_any_value;
+ if (value->max_remaining_users == 1) {
+ m_value_per_output_id[to.id()] = value;
+ m_value_per_output_id[from.id()] = nullptr;
+ value->max_remaining_users = to.targets().size();
+ return *value->vector_array;
+ }
+ }
+
+ GVArraySpan virtual_array_span = this->get_vector_input__single(input);
+
+ GVectorArray *new_vector_array = new GVectorArray(base_type, 1);
+ new_vector_array->extend(0, virtual_array_span[0]);
+
+ OwnVectorValue *new_value = m_allocator.construct<OwnVectorValue>(*new_vector_array,
+ to.targets().size());
+ m_value_per_output_id[to.id()] = new_value;
+ return *new_vector_array;
+}
+
+GVSpan MFNetworkEvaluationStorage::get_single_input__full(const MFInputSocket &socket)
+{
+ const MFOutputSocket &origin = *socket.origin();
+ Value *any_value = m_value_per_output_id[origin.id()];
+ BLI_assert(any_value != nullptr);
+
+ if (any_value->type == ValueType::OwnSingle) {
+ OwnSingleValue *value = (OwnSingleValue *)any_value;
+ if (value->is_single_allocated) {
+ return GVSpan::FromSingle(value->span.type(), value->span.buffer(), m_min_array_size);
+ }
+ else {
+ return value->span;
+ }
+ }
+ else if (any_value->type == ValueType::InputSingle) {
+ InputSingleValue *value = (InputSingleValue *)any_value;
+ return value->virtual_span;
+ }
+ else if (any_value->type == ValueType::OutputSingle) {
+ OutputSingleValue *value = (OutputSingleValue *)any_value;
+ BLI_assert(value->is_computed);
+ return value->span;
+ }
+
+ BLI_assert(false);
+ return GVSpan(CPPType::get<float>());
+}
+
+GVSpan MFNetworkEvaluationStorage::get_single_input__single(const MFInputSocket &socket)
+{
+ const MFOutputSocket &origin = *socket.origin();
+ Value *any_value = m_value_per_output_id[origin.id()];
+ BLI_assert(any_value != nullptr);
+
+ if (any_value->type == ValueType::OwnSingle) {
+ OwnSingleValue *value = (OwnSingleValue *)any_value;
+ BLI_assert(value->span.size() == 1);
+ return value->span;
+ }
+ else if (any_value->type == ValueType::InputSingle) {
+ InputSingleValue *value = (InputSingleValue *)any_value;
+ BLI_assert(value->virtual_span.is_single_element());
+ return value->virtual_span;
+ }
+ else if (any_value->type == ValueType::OutputSingle) {
+ OutputSingleValue *value = (OutputSingleValue *)any_value;
+ BLI_assert(value->is_computed);
+ BLI_assert(value->span.size() == 1);
+ return value->span;
+ }
+
+ BLI_assert(false);
+ return GVSpan(CPPType::get<float>());
+}
+
+GVArraySpan MFNetworkEvaluationStorage::get_vector_input__full(const MFInputSocket &socket)
+{
+ const MFOutputSocket &origin = *socket.origin();
+ Value *any_value = m_value_per_output_id[origin.id()];
+ BLI_assert(any_value != nullptr);
+
+ if (any_value->type == ValueType::OwnVector) {
+ OwnVectorValue *value = (OwnVectorValue *)any_value;
+ if (value->vector_array->size() == 1) {
+ GSpan span = (*value->vector_array)[0];
+ return GVArraySpan(span, m_min_array_size);
+ }
+ else {
+ return *value->vector_array;
+ }
+ }
+ else if (any_value->type == ValueType::InputVector) {
+ InputVectorValue *value = (InputVectorValue *)any_value;
+ return value->virtual_array_span;
+ }
+ else if (any_value->type == ValueType::OutputVector) {
+ OutputVectorValue *value = (OutputVectorValue *)any_value;
+ return *value->vector_array;
+ }
+
+ BLI_assert(false);
+ return GVArraySpan(CPPType::get<float>());
+}
+
+GVArraySpan MFNetworkEvaluationStorage::get_vector_input__single(const MFInputSocket &socket)
+{
+ const MFOutputSocket &origin = *socket.origin();
+ Value *any_value = m_value_per_output_id[origin.id()];
+ BLI_assert(any_value != nullptr);
+
+ if (any_value->type == ValueType::OwnVector) {
+ OwnVectorValue *value = (OwnVectorValue *)any_value;
+ BLI_assert(value->vector_array->size() == 1);
+ return *value->vector_array;
+ }
+ else if (any_value->type == ValueType::InputVector) {
+ InputVectorValue *value = (InputVectorValue *)any_value;
+ BLI_assert(value->virtual_array_span.is_single_array());
+ return value->virtual_array_span;
+ }
+ else if (any_value->type == ValueType::OutputVector) {
+ OutputVectorValue *value = (OutputVectorValue *)any_value;
+ BLI_assert(value->vector_array->size() == 1);
+ return *value->vector_array;
+ }
+
+ BLI_assert(false);
+ return GVArraySpan(CPPType::get<float>());
+}
+
+/** \} */
+
+} // namespace fn
+} // namespace blender