238 lines
7.0 KiB
C++
238 lines
7.0 KiB
C++
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2015 Blender Foundation.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup depsgraph
|
|
*/
|
|
|
|
#include "intern/builder/deg_builder_cycle.h"
|
|
|
|
// TOO(sergey): Use some wrappers over those?
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
|
|
#include "BLI_stack.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "intern/node/deg_node.h"
|
|
#include "intern/node/deg_node_component.h"
|
|
#include "intern/node/deg_node_operation.h"
|
|
|
|
#include "intern/depsgraph.h"
|
|
#include "intern/depsgraph_relation.h"
|
|
|
|
namespace blender::deg {
|
|
|
|
namespace {
|
|
|
|
enum eCyclicCheckVisitedState {
|
|
/* Not is not visited at all during traversal. */
|
|
NODE_NOT_VISITED = 0,
|
|
/* Node has been visited during traversal and not in current stack. */
|
|
NODE_VISITED = 1,
|
|
/* Node has been visited during traversal and is in current stack. */
|
|
NODE_IN_STACK = 2,
|
|
};
|
|
|
|
struct StackEntry {
|
|
OperationNode *node;
|
|
StackEntry *from;
|
|
Relation *via_relation;
|
|
};
|
|
|
|
struct CyclesSolverState {
|
|
CyclesSolverState(Depsgraph *graph)
|
|
: graph(graph),
|
|
traversal_stack(BLI_stack_new(sizeof(StackEntry), "DEG detect cycles stack")),
|
|
num_cycles(0)
|
|
{
|
|
/* pass */
|
|
}
|
|
~CyclesSolverState()
|
|
{
|
|
BLI_stack_free(traversal_stack);
|
|
if (num_cycles != 0) {
|
|
printf("Detected %d dependency cycles\n", num_cycles);
|
|
}
|
|
}
|
|
Depsgraph *graph;
|
|
BLI_Stack *traversal_stack;
|
|
int num_cycles;
|
|
};
|
|
|
|
inline void set_node_visited_state(Node *node, eCyclicCheckVisitedState state)
|
|
{
|
|
node->custom_flags = (node->custom_flags & ~0x3) | (int)state;
|
|
}
|
|
|
|
inline eCyclicCheckVisitedState get_node_visited_state(Node *node)
|
|
{
|
|
return (eCyclicCheckVisitedState)(node->custom_flags & 0x3);
|
|
}
|
|
|
|
inline void set_node_num_visited_children(Node *node, int num_children)
|
|
{
|
|
node->custom_flags = (node->custom_flags & 0x3) | (num_children << 2);
|
|
}
|
|
|
|
inline int get_node_num_visited_children(Node *node)
|
|
{
|
|
return node->custom_flags >> 2;
|
|
}
|
|
|
|
void schedule_node_to_stack(CyclesSolverState *state, OperationNode *node)
|
|
{
|
|
StackEntry entry;
|
|
entry.node = node;
|
|
entry.from = nullptr;
|
|
entry.via_relation = nullptr;
|
|
BLI_stack_push(state->traversal_stack, &entry);
|
|
set_node_visited_state(node, NODE_IN_STACK);
|
|
}
|
|
|
|
/* Schedule leaf nodes (node without input links) for traversal. */
|
|
void schedule_leaf_nodes(CyclesSolverState *state)
|
|
{
|
|
for (OperationNode *node : state->graph->operations) {
|
|
bool has_inlinks = false;
|
|
for (Relation *rel : node->inlinks) {
|
|
if (rel->from->type == NodeType::OPERATION) {
|
|
has_inlinks = true;
|
|
}
|
|
}
|
|
node->custom_flags = 0;
|
|
if (has_inlinks == false) {
|
|
schedule_node_to_stack(state, node);
|
|
}
|
|
else {
|
|
set_node_visited_state(node, NODE_NOT_VISITED);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Schedule node which was not checked yet for being belong to
|
|
* any of dependency cycle.
|
|
*/
|
|
bool schedule_non_checked_node(CyclesSolverState *state)
|
|
{
|
|
for (OperationNode *node : state->graph->operations) {
|
|
if (get_node_visited_state(node) == NODE_NOT_VISITED) {
|
|
schedule_node_to_stack(state, node);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool check_relation_can_murder(Relation *relation)
|
|
{
|
|
if (relation->flag & RELATION_FLAG_GODMODE) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Relation *select_relation_to_murder(Relation *relation, StackEntry *cycle_start_entry)
|
|
{
|
|
/* More or less Russian roulette solver, which will make sure only
|
|
* specially marked relations are kept alive.
|
|
*
|
|
* TODO(sergey): There might be better strategies here. */
|
|
if (check_relation_can_murder(relation)) {
|
|
return relation;
|
|
}
|
|
StackEntry *current = cycle_start_entry;
|
|
OperationNode *to_node = (OperationNode *)relation->to;
|
|
while (current->node != to_node) {
|
|
if (check_relation_can_murder(current->via_relation)) {
|
|
return current->via_relation;
|
|
}
|
|
current = current->from;
|
|
}
|
|
return relation;
|
|
}
|
|
|
|
/* Solve cycles with all nodes which are scheduled for traversal. */
|
|
void solve_cycles(CyclesSolverState *state)
|
|
{
|
|
BLI_Stack *traversal_stack = state->traversal_stack;
|
|
while (!BLI_stack_is_empty(traversal_stack)) {
|
|
StackEntry *entry = (StackEntry *)BLI_stack_peek(traversal_stack);
|
|
OperationNode *node = entry->node;
|
|
bool all_child_traversed = true;
|
|
const int num_visited = get_node_num_visited_children(node);
|
|
for (int i = num_visited; i < node->outlinks.size(); i++) {
|
|
Relation *rel = node->outlinks[i];
|
|
if (rel->to->type == NodeType::OPERATION) {
|
|
OperationNode *to = (OperationNode *)rel->to;
|
|
eCyclicCheckVisitedState to_state = get_node_visited_state(to);
|
|
if (to_state == NODE_IN_STACK) {
|
|
string cycle_str = " " + to->full_identifier() + " depends on\n " +
|
|
node->full_identifier() + " via '" + rel->name + "'\n";
|
|
StackEntry *current = entry;
|
|
while (current->node != to) {
|
|
BLI_assert(current != nullptr);
|
|
cycle_str += " " + current->from->node->full_identifier() + " via '" +
|
|
current->via_relation->name + "'\n";
|
|
current = current->from;
|
|
}
|
|
printf("Dependency cycle detected:\n%s", cycle_str.c_str());
|
|
Relation *sacrificial_relation = select_relation_to_murder(rel, entry);
|
|
sacrificial_relation->flag |= RELATION_FLAG_CYCLIC;
|
|
++state->num_cycles;
|
|
}
|
|
else if (to_state == NODE_NOT_VISITED) {
|
|
StackEntry new_entry;
|
|
new_entry.node = to;
|
|
new_entry.from = entry;
|
|
new_entry.via_relation = rel;
|
|
BLI_stack_push(traversal_stack, &new_entry);
|
|
set_node_visited_state(node, NODE_IN_STACK);
|
|
all_child_traversed = false;
|
|
set_node_num_visited_children(node, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (all_child_traversed) {
|
|
set_node_visited_state(node, NODE_VISITED);
|
|
BLI_stack_discard(traversal_stack);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void deg_graph_detect_cycles(Depsgraph *graph)
|
|
{
|
|
CyclesSolverState state(graph);
|
|
/* First we solve cycles which are reachable from leaf nodes. */
|
|
schedule_leaf_nodes(&state);
|
|
solve_cycles(&state);
|
|
/* We are not done yet. It is possible to have closed loop cycle,
|
|
* for example A -> B -> C -> A. These nodes were not scheduled
|
|
* yet (since they all have inlinks), and were not traversed since
|
|
* nobody else points to them. */
|
|
while (schedule_non_checked_node(&state)) {
|
|
solve_cycles(&state);
|
|
}
|
|
}
|
|
|
|
} // namespace blender::deg
|