Blender V4.3
derived_node_tree.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
6
7#include "BLI_dot_export.hh"
8
9namespace blender::nodes {
10
12{
13 /* Construct all possible contexts immediately. This is significantly cheaper than inlining all
14 * node groups. If it still becomes a performance issue in the future, contexts could be
15 * constructed lazily when they are needed. */
16 root_context_ = &this->construct_context_recursively(
17 nullptr, nullptr, btree, bke::NODE_INSTANCE_KEY_BASE);
18}
19
20DTreeContext &DerivedNodeTree::construct_context_recursively(DTreeContext *parent_context,
21 const bNode *parent_node,
22 const bNodeTree &btree,
23 const bNodeInstanceKey instance_key)
24{
25 btree.ensure_topology_cache();
26 DTreeContext &context = *allocator_.construct<DTreeContext>().release();
27 context.parent_context_ = parent_context;
28 context.parent_node_ = parent_node;
29 context.derived_tree_ = this;
30 context.btree_ = &btree;
31 context.instance_key_ = instance_key;
32 used_btrees_.add(context.btree_);
33
34 for (const bNode *bnode : context.btree_->all_nodes()) {
35 if (bnode->is_group()) {
36 bNodeTree *child_btree = reinterpret_cast<bNodeTree *>(bnode->id);
37 if (child_btree != nullptr) {
38 const bNodeInstanceKey child_key = bke::node_instance_key(instance_key, &btree, bnode);
39 DTreeContext &child = this->construct_context_recursively(
40 &context, bnode, *child_btree, child_key);
41 context.children_.add_new(bnode, &child);
42 }
43 }
44 }
45
46 return context;
47}
48
50{
51 /* Has to be destructed manually, because the context info is allocated in a linear allocator. */
52 this->destruct_context_recursively(root_context_);
53}
54
55void DerivedNodeTree::destruct_context_recursively(DTreeContext *context)
56{
57 for (DTreeContext *child : context->children_.values()) {
58 this->destruct_context_recursively(child);
59 }
60 context->~DTreeContext();
61}
62
64{
65 for (const bNodeTree *btree : used_btrees_) {
66 if (btree->has_available_link_cycle()) {
67 return true;
68 }
69 }
70 return false;
71}
72
74{
75 for (const bNodeTree *btree : used_btrees_) {
76 if (btree->has_undefined_nodes_or_sockets()) {
77 return true;
78 }
79 }
80 return false;
81}
82
84{
85 this->foreach_node_in_context_recursive(*root_context_, callback);
86}
87
88void DerivedNodeTree::foreach_node_in_context_recursive(const DTreeContext &context,
89 FunctionRef<void(DNode)> callback) const
90{
91 for (const bNode *bnode : context.btree_->all_nodes()) {
92 callback(DNode(&context, bnode));
93 }
94 for (const DTreeContext *child_context : context.children_.values()) {
95 this->foreach_node_in_context_recursive(*child_context, callback);
96 }
97}
98
100{
101 return bke::node_instance_key(context()->instance_key(), &context()->btree(), bnode());
102}
103
105{
106 BLI_assert(*this);
107 BLI_assert(bsocket_->owner_node().is_group_output());
108 BLI_assert(bsocket_->index() < bsocket_->owner_node().input_sockets().size() - 1);
109
110 const DTreeContext *parent_context = context_->parent_context();
111 const bNode *parent_node = context_->parent_node();
112 BLI_assert(parent_context != nullptr);
113 BLI_assert(parent_node != nullptr);
114
115 const int socket_index = bsocket_->index();
116 return {parent_context, &parent_node->output_socket(socket_index)};
117}
118
120{
121 BLI_assert(*this);
122 BLI_assert(bsocket_->owner_node().is_group());
123
124 const DTreeContext *child_context = context_->child_context(bsocket_->owner_node());
125 BLI_assert(child_context != nullptr);
126
127 const bNodeTree &child_tree = child_context->btree();
128 Span<const bNode *> group_input_nodes = child_tree.group_input_nodes();
129 const int socket_index = bsocket_->index();
130 Vector<DOutputSocket> sockets;
131 for (const bNode *group_input_node : group_input_nodes) {
132 sockets.append(DOutputSocket(child_context, &group_input_node->output_socket(socket_index)));
133 }
134 return sockets;
135}
136
138{
139 BLI_assert(*this);
140 BLI_assert(bsocket_->owner_node().is_group_input());
141 BLI_assert(bsocket_->index() < bsocket_->owner_node().output_sockets().size() - 1);
142
143 const DTreeContext *parent_context = context_->parent_context();
144 const bNode *parent_node = context_->parent_node();
145 BLI_assert(parent_context != nullptr);
146 BLI_assert(parent_node != nullptr);
147
148 const int socket_index = bsocket_->index();
149 return {parent_context, &parent_node->input_socket(socket_index)};
150}
151
153{
154 BLI_assert(*this);
155 BLI_assert(bsocket_->owner_node().is_group());
156
157 const DTreeContext *child_context = context_->child_context(bsocket_->owner_node());
158 if (child_context == nullptr) {
159 /* Can happen when the group node references a non-existent group (e.g. when the group is
160 * linked but the original file is not found). */
161 return {};
162 }
163
164 const bNodeTree &child_tree = child_context->btree();
165 Span<const bNode *> group_output_nodes = child_tree.nodes_by_type("NodeGroupOutput");
166 const int socket_index = bsocket_->index();
167 for (const bNode *group_output_node : group_output_nodes) {
168 if (group_output_node->flag & NODE_DO_OUTPUT || group_output_nodes.size() == 1) {
169 return {child_context, &group_output_node->input_socket(socket_index)};
170 }
171 }
172 return {};
173}
174
176{
177 BLI_assert(*this);
178 for (const bNodeSocket *linked_socket : bsocket_->logically_linked_sockets()) {
179 const bNode &linked_node = linked_socket->owner_node();
180 DOutputSocket linked_dsocket{context_, linked_socket};
181
182 if (linked_node.is_group_input()) {
183 if (context_->is_root()) {
184 /* This is a group input in the root node group. */
185 origin_fn(linked_dsocket);
186 }
187 else {
188 DInputSocket socket_in_parent_group = linked_dsocket.get_corresponding_group_node_input();
189 if (socket_in_parent_group->is_logically_linked()) {
190 /* Follow the links coming into the corresponding socket on the parent group node. */
191 socket_in_parent_group.foreach_origin_socket(origin_fn);
192 }
193 else {
194 /* The corresponding input on the parent group node is not connected. Therefore, we use
195 * the value of that input socket directly. */
196 origin_fn(socket_in_parent_group);
197 }
198 }
199 }
200 else if (linked_node.is_group()) {
201 DInputSocket socket_in_group = linked_dsocket.get_active_corresponding_group_output_socket();
202 if (socket_in_group) {
203 if (socket_in_group->is_logically_linked()) {
204 /* Follow the links coming into the group output node of the child node group. */
205 socket_in_group.foreach_origin_socket(origin_fn);
206 }
207 else {
208 /* The output of the child node group is not connected, so we have to get the value from
209 * that socket. */
210 origin_fn(socket_in_group);
211 }
212 }
213 }
214 else {
215 /* The normal case: just use the value of a linked output socket. */
216 origin_fn(linked_dsocket);
217 }
218 }
219}
220
222{
223 TargetSocketPathInfo path_info;
224 this->foreach_target_socket(target_fn, path_info);
225}
226
227void DOutputSocket::foreach_target_socket(ForeachTargetSocketFn target_fn,
228 TargetSocketPathInfo &path_info) const
229{
230 for (const bNodeLink *link : bsocket_->directly_linked_links()) {
231 if (link->is_muted()) {
232 continue;
233 }
234 const DInputSocket &linked_socket{context_, link->tosock};
235 if (!linked_socket->is_available()) {
236 continue;
237 }
238 const DNode linked_node = linked_socket.node();
239 if (linked_node->is_reroute()) {
240 const DInputSocket reroute_input = linked_socket;
241 const DOutputSocket reroute_output = linked_node.output(0);
242 path_info.sockets.append(reroute_input);
243 path_info.sockets.append(reroute_output);
244 reroute_output.foreach_target_socket(target_fn, path_info);
245 path_info.sockets.pop_last();
246 path_info.sockets.pop_last();
247 }
248 else if (linked_node->is_muted()) {
249 for (const bNodeLink &internal_link : linked_node->internal_links()) {
250 if (internal_link.fromsock != linked_socket.bsocket()) {
251 continue;
252 }
253 /* The internal link only forwards the first incoming link. */
254 if (linked_socket->is_multi_input()) {
255 if (linked_socket->directly_linked_links()[0] != link) {
256 continue;
257 }
258 }
259 const DInputSocket mute_input = linked_socket;
260 const DOutputSocket mute_output{context_, internal_link.tosock};
261 path_info.sockets.append(mute_input);
262 path_info.sockets.append(mute_output);
263 mute_output.foreach_target_socket(target_fn, path_info);
264 path_info.sockets.pop_last();
265 path_info.sockets.pop_last();
266 }
267 }
268 else if (linked_node->is_group_output()) {
269 if (linked_node.bnode() != context_->btree().group_output_node()) {
270 continue;
271 }
272 if (context_->is_root()) {
273 /* This is a group output in the root node group. */
274 path_info.sockets.append(linked_socket);
275 target_fn(linked_socket, path_info);
276 path_info.sockets.pop_last();
277 }
278 else {
279 /* Follow the links going out of the group node in the parent node group. */
280 const DOutputSocket socket_in_parent_group =
281 linked_socket.get_corresponding_group_node_output();
282 path_info.sockets.append(linked_socket);
283 path_info.sockets.append(socket_in_parent_group);
284 socket_in_parent_group.foreach_target_socket(target_fn, path_info);
285 path_info.sockets.pop_last();
286 path_info.sockets.pop_last();
287 }
288 }
289 else if (linked_node->is_group()) {
290 /* Follow the links within the nested node group. */
291 path_info.sockets.append(linked_socket);
292 const Vector<DOutputSocket> sockets_in_group =
293 linked_socket.get_corresponding_group_input_sockets();
294 for (const DOutputSocket &socket_in_group : sockets_in_group) {
295 path_info.sockets.append(socket_in_group);
296 socket_in_group.foreach_target_socket(target_fn, path_info);
297 path_info.sockets.pop_last();
298 }
299 path_info.sockets.pop_last();
300 }
301 else {
302 /* The normal case: just use the linked input socket as target. */
303 path_info.sockets.append(linked_socket);
304 target_fn(linked_socket, path_info);
305 path_info.sockets.pop_last();
306 }
307 }
308}
309
310/* Find the active context from the given context and its descendants contexts. The active context
311 * is the one whose node instance key matches the active_viewer_key stored in the root node tree.
312 * The instance key of each context is computed by calling node_instance_key given the key of
313 * the parent as well as the group node making the context. */
315{
316 const bNodeInstanceKey key = context->instance_key();
317
318 /* The instance key of the given context matches the active viewer instance key, so this is the
319 * active context, return it. */
320 if (key.value == context->derived_tree().root_context().btree().active_viewer_key.value) {
321 return context;
322 }
323
324 /* For each of the group nodes, compute their instance key and contexts and call this function
325 * recursively. */
326 for (const bNode *group_node : context->btree().group_nodes()) {
327 const DTreeContext *child_context = context->child_context(*group_node);
328 const DTreeContext *found_context = find_active_context_recursive(child_context);
329
330 /* If the found context is null, that means neither the child context nor one of its descendant
331 * contexts is active. */
332 if (!found_context) {
333 continue;
334 }
335
336 /* Otherwise, we have found our active context, return it. */
337 return found_context;
338 }
339
340 /* Neither the given context nor one of its descendant contexts is active, so return null. */
341 return nullptr;
342}
343
345{
346 /* If the active viewer key is bke::NODE_INSTANCE_KEY_NONE, that means it is not yet initialized
347 * and we return the root context in that case. See the find_active_context_recursive function.
348 */
349 if (root_context().btree().active_viewer_key.value == bke::NODE_INSTANCE_KEY_NONE.value) {
350 return root_context();
351 }
352
354}
355
356/* Each nested node group gets its own cluster. Just as node groups, clusters can be nested. */
358 dot::DirectedGraph &digraph,
359 const DTreeContext *context,
361{
362 return dot_clusters.lookup_or_add_cb(context, [&]() -> dot::Cluster * {
363 const DTreeContext *parent_context = context->parent_context();
364 if (parent_context == nullptr) {
365 return nullptr;
366 }
368 digraph, parent_context, dot_clusters);
369 std::string cluster_name = StringRef(context->btree().id.name + 2) + " / " +
370 context->parent_node()->name;
371 dot::Cluster &cluster = digraph.new_cluster(cluster_name);
372 cluster.set_parent_cluster(parent_cluster);
373 return &cluster;
374 });
375}
376
377std::string DerivedNodeTree::to_dot() const
378{
379 dot::DirectedGraph digraph;
380 digraph.set_rankdir(dot::Attr_rankdir::LeftToRight);
381
383 Map<DInputSocket, dot::NodePort> dot_input_sockets;
384 Map<DOutputSocket, dot::NodePort> dot_output_sockets;
385
386 this->foreach_node([&](DNode node) {
387 /* Ignore nodes that should not show up in the final output. */
388 if (node->is_muted() || node->is_group() || node->is_reroute() || node->is_frame()) {
389 return;
390 }
391 if (!node.context()->is_root()) {
392 if (node->is_group_input() || node->is_group_output()) {
393 return;
394 }
395 }
396
397 dot::Cluster *cluster = get_dot_cluster_for_context(digraph, node.context(), dot_clusters);
398
399 dot::Node &dot_node = digraph.new_node("");
400 dot_node.set_parent_cluster(cluster);
401 dot_node.set_background_color("white");
402
403 dot::NodeWithSockets dot_node_with_sockets;
404 for (const bNodeSocket *socket : node->input_sockets()) {
405 if (socket->is_available()) {
406 dot_node_with_sockets.add_input(socket->name);
407 }
408 }
409 for (const bNodeSocket *socket : node->output_sockets()) {
410 if (socket->is_available()) {
411 dot_node_with_sockets.add_output(socket->name);
412 }
413 }
414
415 dot::NodeWithSocketsRef dot_node_with_sockets_ref = dot::NodeWithSocketsRef(
416 dot_node, dot_node_with_sockets);
417
418 int input_index = 0;
419 for (const bNodeSocket *socket : node->input_sockets()) {
420 if (socket->is_available()) {
421 dot_input_sockets.add_new(DInputSocket{node.context(), socket},
422 dot_node_with_sockets_ref.input(input_index));
423 input_index++;
424 }
425 }
426 int output_index = 0;
427 for (const bNodeSocket *socket : node->output_sockets()) {
428 if (socket->is_available()) {
429 dot_output_sockets.add_new(DOutputSocket{node.context(), socket},
430 dot_node_with_sockets_ref.output(output_index));
431 output_index++;
432 }
433 }
434 });
435
436 /* Floating inputs are used for example to visualize unlinked group node inputs. */
437 Map<DSocket, dot::Node *> dot_floating_inputs;
438
439 for (const auto item : dot_input_sockets.items()) {
440 DInputSocket to_socket = item.key;
441 dot::NodePort dot_to_port = item.value;
442 to_socket.foreach_origin_socket([&](DSocket from_socket) {
443 if (from_socket->is_output()) {
444 dot::NodePort *dot_from_port = dot_output_sockets.lookup_ptr(DOutputSocket(from_socket));
445 if (dot_from_port != nullptr) {
446 digraph.new_edge(*dot_from_port, dot_to_port);
447 return;
448 }
449 }
450 dot::Node &dot_node = *dot_floating_inputs.lookup_or_add_cb(from_socket, [&]() {
451 dot::Node &dot_node = digraph.new_node(from_socket->name);
452 dot_node.set_background_color("white");
453 dot_node.set_shape(dot::Attr_shape::Ellipse);
454 dot_node.set_parent_cluster(
455 get_dot_cluster_for_context(digraph, from_socket.context(), dot_clusters));
456 return &dot_node;
457 });
458 digraph.new_edge(dot_node, dot_to_port);
459 });
460 }
461
462 digraph.set_random_cluster_bgcolors();
463
464 return digraph.to_dot_string();
465}
466
467} // namespace blender::nodes
#define BLI_assert(a)
Definition BLI_assert.h:50
@ NODE_DO_OUTPUT
destruct_ptr< T > construct(Args &&...args)
Value & lookup_or_add_cb(const Key &key, const CreateValueF &create_value)
Definition BLI_map.hh:582
void add_new(const Key &key, const Value &value)
Definition BLI_map.hh:241
constexpr int64_t size() const
Definition BLI_span.hh:253
bool add(const Key &key)
void append(const T &value)
void set_parent_cluster(Cluster *new_parent)
Definition dot_export.cc:48
Cluster & new_cluster(StringRef label="")
Definition dot_export.cc:25
Node & new_node(StringRef label)
Definition dot_export.cc:16
void set_rankdir(Attr_rankdir rankdir)
NodePort output(int index) const
NodePort input(int index) const
void set_shape(Attr_shape shape)
void set_background_color(StringRef name)
void set_parent_cluster(Cluster *cluster)
Definition dot_export.cc:68
void foreach_origin_socket(FunctionRef< void(DSocket)> origin_fn) const
DOutputSocket get_corresponding_group_node_output() const
Vector< DOutputSocket, 4 > get_corresponding_group_input_sockets() const
const DTreeContext * context() const
const bNodeInstanceKey instance_key() const
const bNode * bnode() const
DInputSocket get_corresponding_group_node_input() const
DInputSocket get_active_corresponding_group_output_socket() const
void foreach_target_socket(ForeachTargetSocketFn target_fn) const
const DTreeContext * context() const
const DTreeContext * child_context(const bNode &node) const
const DTreeContext * parent_context() const
const bNodeTree & btree() const
const DTreeContext & active_context() const
DerivedNodeTree(const bNodeTree &btree)
const DTreeContext & root_context() const
void foreach_node(FunctionRef< void(DNode)> callback) const
DEGForeachIDComponentCallback callback
const bNodeInstanceKey NODE_INSTANCE_KEY_BASE
Definition node.cc:4020
bNodeInstanceKey node_instance_key(bNodeInstanceKey parent_key, const bNodeTree *ntree, const bNode *node)
Definition node.cc:4041
const bNodeInstanceKey NODE_INSTANCE_KEY_NONE
Definition node.cc:4021
static const DTreeContext * find_active_context_recursive(const DTreeContext *context)
static dot::Cluster * get_dot_cluster_for_context(dot::DirectedGraph &digraph, const DTreeContext *context, Map< const DTreeContext *, dot::Cluster * > &dot_clusters)
unsigned int value
Input & add_input(std::string name)
Output & add_output(std::string name)