Blender V4.3
deg_builder_cycle.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2015 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
10
11// TOO(sergey): Use some wrappers over those?
12#include <cstdio>
13#include <cstdlib>
14
15#include "BLI_stack.h"
16#include "BLI_utildefines.h"
17
21
22#include "intern/depsgraph.hh"
24
25namespace blender::deg {
26
27namespace {
28
29enum eCyclicCheckVisitedState {
30 /* Not is not visited at all during traversal. */
31 NODE_NOT_VISITED = 0,
32 /* Node has been visited during traversal and not in current stack. */
33 NODE_VISITED = 1,
34 /* Node has been visited during traversal and is in current stack. */
35 NODE_IN_STACK = 2,
36};
37
38struct StackEntry {
39 OperationNode *node;
40 StackEntry *from;
41 Relation *via_relation;
42};
43
44struct CyclesSolverState {
45 CyclesSolverState(Depsgraph *graph)
46 : graph(graph), traversal_stack(BLI_stack_new(sizeof(StackEntry), "DEG detect cycles stack"))
47 {
48 /* pass */
49 }
50 ~CyclesSolverState()
51 {
52 BLI_stack_free(traversal_stack);
53 if (num_cycles != 0) {
54 printf("Detected %d dependency cycles\n", num_cycles);
55 }
56 }
57 Depsgraph *graph;
59 int num_cycles = 0;
60};
61
62inline void set_node_visited_state(Node *node, eCyclicCheckVisitedState state)
63{
64 node->custom_flags = (node->custom_flags & ~0x3) | int(state);
65}
66
67inline eCyclicCheckVisitedState get_node_visited_state(Node *node)
68{
69 return (eCyclicCheckVisitedState)(node->custom_flags & 0x3);
70}
71
72inline void set_node_num_visited_children(Node *node, int num_children)
73{
74 node->custom_flags = (node->custom_flags & 0x3) | (num_children << 2);
75}
76
77inline int get_node_num_visited_children(Node *node)
78{
79 return node->custom_flags >> 2;
80}
81
82void schedule_node_to_stack(CyclesSolverState *state, OperationNode *node)
83{
84 StackEntry entry;
85 entry.node = node;
86 entry.from = nullptr;
87 entry.via_relation = nullptr;
88 BLI_stack_push(state->traversal_stack, &entry);
89 set_node_visited_state(node, NODE_IN_STACK);
90}
91
92/* Schedule leaf nodes (node without input links) for traversal. */
93void schedule_leaf_nodes(CyclesSolverState *state)
94{
95 for (OperationNode *node : state->graph->operations) {
96 bool has_inlinks = false;
97 for (Relation *rel : node->inlinks) {
98 if (rel->from->type == NodeType::OPERATION) {
99 has_inlinks = true;
100 }
101 }
102 node->custom_flags = 0;
103 if (has_inlinks == false) {
104 schedule_node_to_stack(state, node);
105 }
106 else {
107 set_node_visited_state(node, NODE_NOT_VISITED);
108 }
109 }
110}
111
112/* Schedule node which was not checked yet for being belong to
113 * any of dependency cycle.
114 */
115bool schedule_non_checked_node(CyclesSolverState *state)
116{
117 for (OperationNode *node : state->graph->operations) {
118 if (get_node_visited_state(node) == NODE_NOT_VISITED) {
119 schedule_node_to_stack(state, node);
120 return true;
121 }
122 }
123 return false;
124}
125
126bool check_relation_can_murder(Relation *relation)
127{
128 if (relation->flag & RELATION_FLAG_GODMODE) {
129 return false;
130 }
131 return true;
132}
133
134Relation *select_relation_to_murder(Relation *relation, StackEntry *cycle_start_entry)
135{
136 /* More or less Russian roulette solver, which will make sure only
137 * specially marked relations are kept alive.
138 *
139 * TODO(sergey): There might be better strategies here. */
140 if (check_relation_can_murder(relation)) {
141 return relation;
142 }
143 StackEntry *current = cycle_start_entry;
144 OperationNode *to_node = (OperationNode *)relation->to;
145 while (current->node != to_node) {
146 if (check_relation_can_murder(current->via_relation)) {
147 return current->via_relation;
148 }
149 current = current->from;
150 }
151 return relation;
152}
153
154/* Solve cycles with all nodes which are scheduled for traversal. */
155void solve_cycles(CyclesSolverState *state)
156{
157 BLI_Stack *traversal_stack = state->traversal_stack;
159 StackEntry *entry = (StackEntry *)BLI_stack_peek(traversal_stack);
160 OperationNode *node = entry->node;
161 bool all_child_traversed = true;
162 const int num_visited = get_node_num_visited_children(node);
163 for (int i = num_visited; i < node->outlinks.size(); i++) {
164 Relation *rel = node->outlinks[i];
165 if (rel->to->type == NodeType::OPERATION) {
166 OperationNode *to = (OperationNode *)rel->to;
167 eCyclicCheckVisitedState to_state = get_node_visited_state(to);
168 if (to_state == NODE_IN_STACK) {
169 string cycle_str = " " + to->full_identifier() + " depends on\n " +
170 node->full_identifier() + " via '" + rel->name + "'\n";
171 StackEntry *current = entry;
172 while (current->node != to) {
173 BLI_assert(current != nullptr);
174 cycle_str += " " + current->from->node->full_identifier() + " via '" +
175 current->via_relation->name + "'\n";
176 current = current->from;
177 }
178 printf("Dependency cycle detected:\n%s", cycle_str.c_str());
179 Relation *sacrificial_relation = select_relation_to_murder(rel, entry);
180 sacrificial_relation->flag |= RELATION_FLAG_CYCLIC;
181 ++state->num_cycles;
182 }
183 else if (to_state == NODE_NOT_VISITED) {
184 StackEntry new_entry;
185 new_entry.node = to;
186 new_entry.from = entry;
187 new_entry.via_relation = rel;
188 BLI_stack_push(traversal_stack, &new_entry);
189 set_node_visited_state(node, NODE_IN_STACK);
190 all_child_traversed = false;
191 set_node_num_visited_children(node, i);
192 break;
193 }
194 }
195 }
196 if (all_child_traversed) {
197 set_node_visited_state(node, NODE_VISITED);
199 }
200 }
201}
202
203} // namespace
204
206{
207 CyclesSolverState state(graph);
208 /* First we solve cycles which are reachable from leaf nodes. */
209 schedule_leaf_nodes(&state);
210 solve_cycles(&state);
211 /* We are not done yet. It is possible to have closed loop cycle,
212 * for example A -> B -> C -> A. These nodes were not scheduled
213 * yet (since they all have inlinks), and were not traversed since
214 * nobody else points to them. */
215 while (schedule_non_checked_node(&state)) {
216 solve_cycles(&state);
217 }
218}
219
220} // namespace blender::deg
#define BLI_assert(a)
Definition BLI_assert.h:50
void BLI_stack_push(BLI_Stack *stack, const void *src) ATTR_NONNULL()
Definition stack.c:131
bool BLI_stack_is_empty(const BLI_Stack *stack) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition stack.c:249
void BLI_stack_free(BLI_Stack *stack) ATTR_NONNULL()
Definition stack.c:96
void * BLI_stack_peek(BLI_Stack *stack) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition stack.c:168
void BLI_stack_discard(BLI_Stack *stack) ATTR_NONNULL()
Definition stack.c:175
#define BLI_stack_new(esize, descr)
#define printf
int num_cycles
OperationNode * node
BLI_Stack * traversal_stack
Relation * via_relation
static ulong state[N]
void deg_graph_detect_cycles(Depsgraph *graph)