The multi-function network system was able to compose multiple multi-functions into a new one and to evaluate that efficiently. This functionality was heavily used by the particle nodes prototype a year ago. However, since then we only used multi-functions without the need to compose them in geometry nodes. The upcoming "fields" in geometry nodes will need a way to compose multi-functions again. Unfortunately, the code removed in this commit was not ideal for this different kind of function composition. I've been working on an alternative that will be added separately when it becomes needed. I've had to update all the function nodes, because their interface depended on the multi-function network data structure a bit. The actual multi-function implementations are still the same though.
333 lines
8.7 KiB
C++
333 lines
8.7 KiB
C++
/* Apache License, Version 2.0 */
|
|
|
|
#include "testing/testing.h"
|
|
|
|
#include "FN_multi_function.hh"
|
|
#include "FN_multi_function_builder.hh"
|
|
#include "FN_multi_function_test_common.hh"
|
|
|
|
namespace blender::fn::tests {
|
|
namespace {
|
|
|
|
class AddFunction : public MultiFunction {
|
|
public:
|
|
AddFunction()
|
|
{
|
|
static MFSignature signature = create_signature();
|
|
this->set_signature(&signature);
|
|
}
|
|
|
|
static MFSignature create_signature()
|
|
{
|
|
MFSignatureBuilder signature("Add");
|
|
signature.single_input<int>("A");
|
|
signature.single_input<int>("B");
|
|
signature.single_output<int>("Result");
|
|
return signature.build();
|
|
}
|
|
|
|
void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
|
|
{
|
|
const VArray<int> &a = params.readonly_single_input<int>(0, "A");
|
|
const VArray<int> &b = params.readonly_single_input<int>(1, "B");
|
|
MutableSpan<int> result = params.uninitialized_single_output<int>(2, "Result");
|
|
|
|
for (int64_t i : mask) {
|
|
result[i] = a[i] + b[i];
|
|
}
|
|
}
|
|
};
|
|
|
|
TEST(multi_function, AddFunction)
|
|
{
|
|
AddFunction fn;
|
|
|
|
Array<int> input1 = {4, 5, 6};
|
|
Array<int> input2 = {10, 20, 30};
|
|
Array<int> output(3, -1);
|
|
|
|
MFParamsBuilder params(fn, 3);
|
|
params.add_readonly_single_input(input1.as_span());
|
|
params.add_readonly_single_input(input2.as_span());
|
|
params.add_uninitialized_single_output(output.as_mutable_span());
|
|
|
|
MFContextBuilder context;
|
|
|
|
fn.call({0, 2}, params, context);
|
|
|
|
EXPECT_EQ(output[0], 14);
|
|
EXPECT_EQ(output[1], -1);
|
|
EXPECT_EQ(output[2], 36);
|
|
}
|
|
|
|
TEST(multi_function, AddPrefixFunction)
|
|
{
|
|
AddPrefixFunction fn;
|
|
|
|
Array<std::string> strings = {
|
|
"Hello",
|
|
"World",
|
|
"This is a test",
|
|
"Another much longer string to trigger an allocation",
|
|
};
|
|
|
|
std::string prefix = "AB";
|
|
|
|
MFParamsBuilder params(fn, strings.size());
|
|
params.add_readonly_single_input(&prefix);
|
|
params.add_single_mutable(strings.as_mutable_span());
|
|
|
|
MFContextBuilder context;
|
|
|
|
fn.call({0, 2, 3}, params, context);
|
|
|
|
EXPECT_EQ(strings[0], "ABHello");
|
|
EXPECT_EQ(strings[1], "World");
|
|
EXPECT_EQ(strings[2], "ABThis is a test");
|
|
EXPECT_EQ(strings[3], "ABAnother much longer string to trigger an allocation");
|
|
}
|
|
|
|
TEST(multi_function, CreateRangeFunction)
|
|
{
|
|
CreateRangeFunction fn;
|
|
|
|
GVectorArray ranges(CPPType::get<int>(), 5);
|
|
GVectorArray_TypedMutableRef<int> ranges_ref{ranges};
|
|
Array<int> sizes = {3, 0, 6, 1, 4};
|
|
|
|
MFParamsBuilder params(fn, ranges.size());
|
|
params.add_readonly_single_input(sizes.as_span());
|
|
params.add_vector_output(ranges);
|
|
|
|
MFContextBuilder context;
|
|
|
|
fn.call({0, 1, 2, 3}, params, context);
|
|
|
|
EXPECT_EQ(ranges[0].size(), 3);
|
|
EXPECT_EQ(ranges[1].size(), 0);
|
|
EXPECT_EQ(ranges[2].size(), 6);
|
|
EXPECT_EQ(ranges[3].size(), 1);
|
|
EXPECT_EQ(ranges[4].size(), 0);
|
|
|
|
EXPECT_EQ(ranges_ref[0][0], 0);
|
|
EXPECT_EQ(ranges_ref[0][1], 1);
|
|
EXPECT_EQ(ranges_ref[0][2], 2);
|
|
EXPECT_EQ(ranges_ref[2][0], 0);
|
|
EXPECT_EQ(ranges_ref[2][1], 1);
|
|
}
|
|
|
|
TEST(multi_function, GenericAppendFunction)
|
|
{
|
|
GenericAppendFunction fn(CPPType::get<int32_t>());
|
|
|
|
GVectorArray vectors(CPPType::get<int32_t>(), 4);
|
|
GVectorArray_TypedMutableRef<int> vectors_ref{vectors};
|
|
vectors_ref.append(0, 1);
|
|
vectors_ref.append(0, 2);
|
|
vectors_ref.append(2, 6);
|
|
Array<int> values = {5, 7, 3, 1};
|
|
|
|
MFParamsBuilder params(fn, vectors.size());
|
|
params.add_vector_mutable(vectors);
|
|
params.add_readonly_single_input(values.as_span());
|
|
|
|
MFContextBuilder context;
|
|
|
|
fn.call(IndexRange(vectors.size()), params, context);
|
|
|
|
EXPECT_EQ(vectors[0].size(), 3);
|
|
EXPECT_EQ(vectors[1].size(), 1);
|
|
EXPECT_EQ(vectors[2].size(), 2);
|
|
EXPECT_EQ(vectors[3].size(), 1);
|
|
|
|
EXPECT_EQ(vectors_ref[0][0], 1);
|
|
EXPECT_EQ(vectors_ref[0][1], 2);
|
|
EXPECT_EQ(vectors_ref[0][2], 5);
|
|
EXPECT_EQ(vectors_ref[1][0], 7);
|
|
EXPECT_EQ(vectors_ref[2][0], 6);
|
|
EXPECT_EQ(vectors_ref[2][1], 3);
|
|
EXPECT_EQ(vectors_ref[3][0], 1);
|
|
}
|
|
|
|
TEST(multi_function, CustomMF_SI_SO)
|
|
{
|
|
CustomMF_SI_SO<std::string, uint> fn("strlen",
|
|
[](const std::string &str) { return str.size(); });
|
|
|
|
Array<std::string> strings = {"hello", "world", "test", "another test"};
|
|
Array<uint> sizes(strings.size(), 0);
|
|
|
|
MFParamsBuilder params(fn, strings.size());
|
|
params.add_readonly_single_input(strings.as_span());
|
|
params.add_uninitialized_single_output(sizes.as_mutable_span());
|
|
|
|
MFContextBuilder context;
|
|
|
|
fn.call(IndexRange(strings.size()), params, context);
|
|
|
|
EXPECT_EQ(sizes[0], 5);
|
|
EXPECT_EQ(sizes[1], 5);
|
|
EXPECT_EQ(sizes[2], 4);
|
|
EXPECT_EQ(sizes[3], 12);
|
|
}
|
|
|
|
TEST(multi_function, CustomMF_SI_SI_SO)
|
|
{
|
|
CustomMF_SI_SI_SO<int, int, int> fn("mul", [](int a, int b) { return a * b; });
|
|
|
|
Array<int> values_a = {4, 6, 8, 9};
|
|
int value_b = 10;
|
|
Array<int> outputs(values_a.size(), -1);
|
|
|
|
MFParamsBuilder params(fn, values_a.size());
|
|
params.add_readonly_single_input(values_a.as_span());
|
|
params.add_readonly_single_input(&value_b);
|
|
params.add_uninitialized_single_output(outputs.as_mutable_span());
|
|
|
|
MFContextBuilder context;
|
|
|
|
fn.call({0, 1, 3}, params, context);
|
|
|
|
EXPECT_EQ(outputs[0], 40);
|
|
EXPECT_EQ(outputs[1], 60);
|
|
EXPECT_EQ(outputs[2], -1);
|
|
EXPECT_EQ(outputs[3], 90);
|
|
}
|
|
|
|
TEST(multi_function, CustomMF_SI_SI_SI_SO)
|
|
{
|
|
CustomMF_SI_SI_SI_SO<int, std::string, bool, uint> fn{
|
|
"custom",
|
|
[](int a, const std::string &b, bool c) { return (uint)((uint)a + b.size() + (uint)c); }};
|
|
|
|
Array<int> values_a = {5, 7, 3, 8};
|
|
Array<std::string> values_b = {"hello", "world", "another", "test"};
|
|
Array<bool> values_c = {true, false, false, true};
|
|
Array<uint> outputs(values_a.size(), 0);
|
|
|
|
MFParamsBuilder params(fn, values_a.size());
|
|
params.add_readonly_single_input(values_a.as_span());
|
|
params.add_readonly_single_input(values_b.as_span());
|
|
params.add_readonly_single_input(values_c.as_span());
|
|
params.add_uninitialized_single_output(outputs.as_mutable_span());
|
|
|
|
MFContextBuilder context;
|
|
|
|
fn.call({1, 2, 3}, params, context);
|
|
|
|
EXPECT_EQ(outputs[0], 0);
|
|
EXPECT_EQ(outputs[1], 12);
|
|
EXPECT_EQ(outputs[2], 10);
|
|
EXPECT_EQ(outputs[3], 13);
|
|
}
|
|
|
|
TEST(multi_function, CustomMF_SM)
|
|
{
|
|
CustomMF_SM<std::string> fn("AddSuffix", [](std::string &value) { value += " test"; });
|
|
|
|
Array<std::string> values = {"a", "b", "c", "d", "e"};
|
|
|
|
MFParamsBuilder params(fn, values.size());
|
|
params.add_single_mutable(values.as_mutable_span());
|
|
|
|
MFContextBuilder context;
|
|
|
|
fn.call({1, 2, 3}, params, context);
|
|
|
|
EXPECT_EQ(values[0], "a");
|
|
EXPECT_EQ(values[1], "b test");
|
|
EXPECT_EQ(values[2], "c test");
|
|
EXPECT_EQ(values[3], "d test");
|
|
EXPECT_EQ(values[4], "e");
|
|
}
|
|
|
|
TEST(multi_function, CustomMF_Constant)
|
|
{
|
|
CustomMF_Constant<int> fn{42};
|
|
|
|
Array<int> outputs(4, 0);
|
|
|
|
MFParamsBuilder params(fn, outputs.size());
|
|
params.add_uninitialized_single_output(outputs.as_mutable_span());
|
|
|
|
MFContextBuilder context;
|
|
|
|
fn.call({0, 2, 3}, params, context);
|
|
|
|
EXPECT_EQ(outputs[0], 42);
|
|
EXPECT_EQ(outputs[1], 0);
|
|
EXPECT_EQ(outputs[2], 42);
|
|
EXPECT_EQ(outputs[3], 42);
|
|
}
|
|
|
|
TEST(multi_function, CustomMF_GenericConstant)
|
|
{
|
|
int value = 42;
|
|
CustomMF_GenericConstant fn{CPPType::get<int32_t>(), (const void *)&value};
|
|
EXPECT_EQ(fn.param_name(0), "42");
|
|
|
|
Array<int> outputs(4, 0);
|
|
|
|
MFParamsBuilder params(fn, outputs.size());
|
|
params.add_uninitialized_single_output(outputs.as_mutable_span());
|
|
|
|
MFContextBuilder context;
|
|
|
|
fn.call({0, 1, 2}, params, context);
|
|
|
|
EXPECT_EQ(outputs[0], 42);
|
|
EXPECT_EQ(outputs[1], 42);
|
|
EXPECT_EQ(outputs[2], 42);
|
|
EXPECT_EQ(outputs[3], 0);
|
|
}
|
|
|
|
TEST(multi_function, CustomMF_GenericConstantArray)
|
|
{
|
|
std::array<int, 4> values = {3, 4, 5, 6};
|
|
CustomMF_GenericConstantArray fn{GSpan(Span(values))};
|
|
EXPECT_EQ(fn.param_name(0), "[3, 4, 5, 6, ]");
|
|
|
|
GVectorArray vector_array{CPPType::get<int32_t>(), 4};
|
|
GVectorArray_TypedMutableRef<int> vector_array_ref{vector_array};
|
|
|
|
MFParamsBuilder params(fn, vector_array.size());
|
|
params.add_vector_output(vector_array);
|
|
|
|
MFContextBuilder context;
|
|
|
|
fn.call({1, 2, 3}, params, context);
|
|
|
|
EXPECT_EQ(vector_array[0].size(), 0);
|
|
EXPECT_EQ(vector_array[1].size(), 4);
|
|
EXPECT_EQ(vector_array[2].size(), 4);
|
|
EXPECT_EQ(vector_array[3].size(), 4);
|
|
for (int i = 1; i < 4; i++) {
|
|
EXPECT_EQ(vector_array_ref[i][0], 3);
|
|
EXPECT_EQ(vector_array_ref[i][1], 4);
|
|
EXPECT_EQ(vector_array_ref[i][2], 5);
|
|
EXPECT_EQ(vector_array_ref[i][3], 6);
|
|
}
|
|
}
|
|
|
|
TEST(multi_function, CustomMF_Convert)
|
|
{
|
|
CustomMF_Convert<float, int> fn;
|
|
|
|
Array<float> inputs = {5.4f, 7.1f, 9.0f};
|
|
Array<int> outputs(inputs.size(), 0);
|
|
|
|
MFParamsBuilder params(fn, inputs.size());
|
|
params.add_readonly_single_input(inputs.as_span());
|
|
params.add_uninitialized_single_output(outputs.as_mutable_span());
|
|
|
|
MFContextBuilder context;
|
|
fn.call({0, 2}, params, context);
|
|
|
|
EXPECT_EQ(outputs[0], 5);
|
|
EXPECT_EQ(outputs[1], 0);
|
|
EXPECT_EQ(outputs[2], 9);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace blender::fn::tests
|