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