Blender  V2.93
deg_builder_cycle.cc
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2015 Blender Foundation.
17  * All rights reserved.
18  */
19 
25 
26 // TOO(sergey): Use some wrappers over those?
27 #include <cstdio>
28 #include <cstdlib>
29 
30 #include "BLI_stack.h"
31 #include "BLI_utildefines.h"
32 
33 #include "intern/node/deg_node.h"
36 
37 #include "intern/depsgraph.h"
39 
40 namespace blender::deg {
41 
42 namespace {
43 
44 enum eCyclicCheckVisitedState {
45  /* Not is not visited at all during traversal. */
46  NODE_NOT_VISITED = 0,
47  /* Node has been visited during traversal and not in current stack. */
48  NODE_VISITED = 1,
49  /* Node has been visited during traversal and is in current stack. */
50  NODE_IN_STACK = 2,
51 };
52 
53 struct StackEntry {
54  OperationNode *node;
55  StackEntry *from;
56  Relation *via_relation;
57 };
58 
59 struct CyclesSolverState {
60  CyclesSolverState(Depsgraph *graph)
61  : graph(graph),
62  traversal_stack(BLI_stack_new(sizeof(StackEntry), "DEG detect cycles stack")),
63  num_cycles(0)
64  {
65  /* pass */
66  }
67  ~CyclesSolverState()
68  {
70  if (num_cycles != 0) {
71  printf("Detected %d dependency cycles\n", num_cycles);
72  }
73  }
77 };
78 
79 inline void set_node_visited_state(Node *node, eCyclicCheckVisitedState state)
80 {
81  node->custom_flags = (node->custom_flags & ~0x3) | (int)state;
82 }
83 
84 inline eCyclicCheckVisitedState get_node_visited_state(Node *node)
85 {
86  return (eCyclicCheckVisitedState)(node->custom_flags & 0x3);
87 }
88 
89 inline void set_node_num_visited_children(Node *node, int num_children)
90 {
91  node->custom_flags = (node->custom_flags & 0x3) | (num_children << 2);
92 }
93 
94 inline int get_node_num_visited_children(Node *node)
95 {
96  return node->custom_flags >> 2;
97 }
98 
99 void schedule_node_to_stack(CyclesSolverState *state, OperationNode *node)
100 {
101  StackEntry entry;
102  entry.node = node;
103  entry.from = nullptr;
104  entry.via_relation = nullptr;
105  BLI_stack_push(state->traversal_stack, &entry);
106  set_node_visited_state(node, NODE_IN_STACK);
107 }
108 
109 /* Schedule leaf nodes (node without input links) for traversal. */
110 void schedule_leaf_nodes(CyclesSolverState *state)
111 {
112  for (OperationNode *node : state->graph->operations) {
113  bool has_inlinks = false;
114  for (Relation *rel : node->inlinks) {
115  if (rel->from->type == NodeType::OPERATION) {
116  has_inlinks = true;
117  }
118  }
119  node->custom_flags = 0;
120  if (has_inlinks == false) {
121  schedule_node_to_stack(state, node);
122  }
123  else {
124  set_node_visited_state(node, NODE_NOT_VISITED);
125  }
126  }
127 }
128 
129 /* Schedule node which was not checked yet for being belong to
130  * any of dependency cycle.
131  */
132 bool schedule_non_checked_node(CyclesSolverState *state)
133 {
134  for (OperationNode *node : state->graph->operations) {
135  if (get_node_visited_state(node) == NODE_NOT_VISITED) {
136  schedule_node_to_stack(state, node);
137  return true;
138  }
139  }
140  return false;
141 }
142 
143 bool check_relation_can_murder(Relation *relation)
144 {
145  if (relation->flag & RELATION_FLAG_GODMODE) {
146  return false;
147  }
148  return true;
149 }
150 
151 Relation *select_relation_to_murder(Relation *relation, StackEntry *cycle_start_entry)
152 {
153  /* More or less Russian roulette solver, which will make sure only
154  * specially marked relations are kept alive.
155  *
156  * TODO(sergey): There might be better strategies here. */
157  if (check_relation_can_murder(relation)) {
158  return relation;
159  }
160  StackEntry *current = cycle_start_entry;
161  OperationNode *to_node = (OperationNode *)relation->to;
162  while (current->node != to_node) {
163  if (check_relation_can_murder(current->via_relation)) {
164  return current->via_relation;
165  }
166  current = current->from;
167  }
168  return relation;
169 }
170 
171 /* Solve cycles with all nodes which are scheduled for traversal. */
172 void solve_cycles(CyclesSolverState *state)
173 {
174  BLI_Stack *traversal_stack = state->traversal_stack;
176  StackEntry *entry = (StackEntry *)BLI_stack_peek(traversal_stack);
177  OperationNode *node = entry->node;
178  bool all_child_traversed = true;
179  const int num_visited = get_node_num_visited_children(node);
180  for (int i = num_visited; i < node->outlinks.size(); i++) {
181  Relation *rel = node->outlinks[i];
182  if (rel->to->type == NodeType::OPERATION) {
183  OperationNode *to = (OperationNode *)rel->to;
184  eCyclicCheckVisitedState to_state = get_node_visited_state(to);
185  if (to_state == NODE_IN_STACK) {
186  string cycle_str = " " + to->full_identifier() + " depends on\n " +
187  node->full_identifier() + " via '" + rel->name + "'\n";
188  StackEntry *current = entry;
189  while (current->node != to) {
190  BLI_assert(current != nullptr);
191  cycle_str += " " + current->from->node->full_identifier() + " via '" +
192  current->via_relation->name + "'\n";
193  current = current->from;
194  }
195  printf("Dependency cycle detected:\n%s", cycle_str.c_str());
196  Relation *sacrificial_relation = select_relation_to_murder(rel, entry);
197  sacrificial_relation->flag |= RELATION_FLAG_CYCLIC;
198  ++state->num_cycles;
199  }
200  else if (to_state == NODE_NOT_VISITED) {
201  StackEntry new_entry;
202  new_entry.node = to;
203  new_entry.from = entry;
204  new_entry.via_relation = rel;
205  BLI_stack_push(traversal_stack, &new_entry);
206  set_node_visited_state(node, NODE_IN_STACK);
207  all_child_traversed = false;
208  set_node_num_visited_children(node, i);
209  break;
210  }
211  }
212  }
213  if (all_child_traversed) {
214  set_node_visited_state(node, NODE_VISITED);
216  }
217  }
218 }
219 
220 } // namespace
221 
223 {
224  CyclesSolverState state(graph);
225  /* First we solve cycles which are reachable from leaf nodes. */
226  schedule_leaf_nodes(&state);
227  solve_cycles(&state);
228  /* We are not done yet. It is possible to have closed loop cycle,
229  * for example A -> B -> C -> A. These nodes were not scheduled
230  * yet (since they all have inlinks), and were not traversed since
231  * nobody else points to them. */
232  while (schedule_non_checked_node(&state)) {
233  solve_cycles(&state);
234  }
235 }
236 
237 } // namespace blender::deg
#define BLI_assert(a)
Definition: BLI_assert.h:58
void * BLI_stack_peek(BLI_Stack *stack) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition: stack.c:220
void BLI_stack_push(BLI_Stack *stack, const void *src) ATTR_NONNULL()
Definition: stack.c:163
bool BLI_stack_is_empty(const BLI_Stack *stack) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition: stack.c:310
void BLI_stack_free(BLI_Stack *stack) ATTR_NONNULL()
Definition: stack.c:114
void BLI_stack_discard(BLI_Stack *stack) ATTR_NONNULL()
Definition: stack.c:230
#define BLI_stack_new(esize, descr)
struct Depsgraph Depsgraph
Definition: DEG_depsgraph.h:51
int64_t size() const
Definition: BLI_vector.hh:662
int num_cycles
OperationNode * node
BLI_Stack * traversal_stack
Depsgraph * graph
StackEntry * from
Relation * via_relation
static ulong state[N]
void deg_graph_detect_cycles(Depsgraph *graph)
Definition: node.h:98
Relations inlinks
Definition: deg_node.h:175
Relations outlinks
Definition: deg_node.h:176