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