Blender V5.0
FN_lazy_function_test.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#include "testing/testing.h"
6
10
11#include "BLI_task.h"
12
14
16 public:
18 {
19 debug_name_ = "Add";
20 inputs_.append({"A", CPPType::get<int>()});
21 inputs_.append({"B", CPPType::get<int>()});
22 outputs_.append({"Result", CPPType::get<int>()});
23 }
24
25 void execute_impl(Params &params, const Context & /*context*/) const override
26 {
27 const int a = params.get_input<int>(0);
28 const int b = params.get_input<int>(1);
29 params.set_output(0, a + b);
30 }
31};
32
34 private:
35 int *dst1_;
36 int *dst2_;
37
38 public:
39 StoreValueFunction(int *dst1, int *dst2) : dst1_(dst1), dst2_(dst2)
40 {
41 debug_name_ = "Store Value";
42 inputs_.append({"A", CPPType::get<int>()});
44 }
45
46 void execute_impl(Params &params, const Context & /*context*/) const override
47 {
48 *dst1_ = params.get_input<int>(0);
49 if (int *value = params.try_get_input_data_ptr_or_request<int>(1)) {
50 *dst2_ = *value;
51 }
52 }
53};
54
56 private:
57 Vector<const FunctionNode *> side_effect_nodes_;
58
59 public:
61 : side_effect_nodes_(side_effect_nodes)
62 {
63 }
64
66 const Context & /*context*/) const override
67 {
68 return side_effect_nodes_;
69 }
70};
71
73{
74 const AddLazyFunction add_fn;
75 int result = 0;
77 add_fn, nullptr, nullptr, std::make_tuple(30, 5), std::make_tuple(&result));
78 EXPECT_EQ(result, 35);
79}
80
81TEST(lazy_function, SideEffects)
82{
84 int dst1 = 0;
85 int dst2 = 0;
86
87 const AddLazyFunction add_fn;
88 const StoreValueFunction store_fn{&dst1, &dst2};
89
90 Graph graph;
91 FunctionNode &add_node_1 = graph.add_function(add_fn);
92 FunctionNode &add_node_2 = graph.add_function(add_fn);
93 FunctionNode &store_node = graph.add_function(store_fn);
94 GraphInputSocket &graph_input = graph.add_input(CPPType::get<int>());
95
96 graph.add_link(graph_input, add_node_1.input(0));
97 graph.add_link(graph_input, add_node_2.input(0));
98 graph.add_link(add_node_1.output(0), store_node.input(0));
99 graph.add_link(add_node_2.output(0), store_node.input(1));
100
101 const int value_10 = 10;
102 const int value_100 = 100;
103 add_node_1.input(1).set_default_value(&value_10);
104 add_node_2.input(1).set_default_value(&value_100);
105
106 graph.update_node_indices();
107
108 SimpleSideEffectProvider side_effect_provider{{&store_node}};
109
110 GraphExecutor executor_fn{graph, {&graph_input}, {}, nullptr, &side_effect_provider, nullptr};
112 executor_fn, nullptr, nullptr, std::make_tuple(5), std::make_tuple());
113
114 EXPECT_EQ(dst1, 15);
115 EXPECT_EQ(dst2, 105);
116}
117
119 public:
121 {
122 debug_name_ = "Partial Evaluation";
124
125 inputs_.append_as("A", CPPType::get<int>(), ValueUsage::Used);
126 inputs_.append_as("B", CPPType::get<int>(), ValueUsage::Used);
127
128 outputs_.append_as("A*2", CPPType::get<int>());
129 outputs_.append_as("B*5", CPPType::get<int>());
130 }
131
132 void execute_impl(Params &params, const Context & /*context*/) const override
133 {
134 if (!params.output_was_set(0)) {
135 if (int *a = params.try_get_input_data_ptr<int>(0)) {
136 params.set_output(0, *a * 2);
137 }
138 }
139 if (!params.output_was_set(1)) {
140 if (int *b = params.try_get_input_data_ptr<int>(1)) {
141 params.set_output(1, *b * 5);
142 }
143 }
144 }
145
146 void possible_output_dependencies(const int output_index,
147 FunctionRef<void(Span<int>)> fn) const override
148 {
149 /* Each output only depends on the input with the same index. */
150 const int input_index = output_index;
151 fn({input_index});
152 }
153};
154
155TEST(lazy_function, GraphWithCycle)
156{
158
159 Graph graph;
160 FunctionNode &fn_node = graph.add_function(fn);
161
162 GraphInputSocket &input_socket = graph.add_input(CPPType::get<int>());
163 GraphOutputSocket &output_socket = graph.add_output(CPPType::get<int>());
164
165 graph.add_link(input_socket, fn_node.input(0));
166 /* NOTE: This creates a cycle in the graph. However, it should still be possible to evaluate it,
167 * because there is no actual data dependency in the cycle. */
168 graph.add_link(fn_node.output(0), fn_node.input(1));
169 graph.add_link(fn_node.output(1), output_socket);
170
171 graph.update_node_indices();
172
173 GraphExecutor executor_fn{graph, {&input_socket}, {&output_socket}, nullptr, nullptr, nullptr};
174 int result = 0;
176 executor_fn, nullptr, nullptr, std::make_tuple(10), std::make_tuple(&result));
177
178 EXPECT_EQ(result, 10 * 2 * 5);
179}
180
181} // namespace blender::fn::lazy_function::tests
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
void BLI_task_scheduler_init(void)
static const CPPType & get()
FunctionNode & add_function(const LazyFunction &fn)
void add_link(OutputSocket &from, InputSocket &to)
GraphOutputSocket & add_output(const CPPType &type, std::string name="")
GraphInputSocket & add_input(const CPPType &type, std::string name="")
const InputSocket & input(int index) const
const OutputSocket & output(int index) const
void execute_impl(Params &params, const Context &) const override
void execute_impl(Params &params, const Context &) const override
void possible_output_dependencies(const int output_index, FunctionRef< void(Span< int >)> fn) const override
SimpleSideEffectProvider(Span< const FunctionNode * > side_effect_nodes)
Vector< const FunctionNode * > get_nodes_with_side_effects(const Context &) const override
void execute_impl(Params &params, const Context &) const override
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void execute_lazy_function_eagerly(const LazyFunction &fn, UserData *user_data, LocalUserData *local_user_data, std::tuple< Inputs... > inputs, std::tuple< Outputs *... > outputs)