Fix T101214: hidden link can cause cycle in node tree
Links that are linked to unavailable sockets should be ignored.
This commit is contained in:
@@ -81,7 +81,7 @@ class bNodeTreeRuntime : NonCopyable, NonMovable {
|
||||
Vector<bNode *> toposort_left_to_right;
|
||||
Vector<bNode *> toposort_right_to_left;
|
||||
Vector<bNode *> group_nodes;
|
||||
bool has_link_cycle = false;
|
||||
bool has_available_link_cycle = false;
|
||||
bool has_undefined_nodes_or_sockets = false;
|
||||
bNode *group_output_node = nullptr;
|
||||
};
|
||||
@@ -152,8 +152,8 @@ class bNodeRuntime : NonCopyable, NonMovable {
|
||||
Map<StringRefNull, bNodeSocket *> inputs_by_identifier;
|
||||
Map<StringRefNull, bNodeSocket *> outputs_by_identifier;
|
||||
int index_in_tree = -1;
|
||||
bool has_linked_inputs = false;
|
||||
bool has_linked_outputs = false;
|
||||
bool has_available_linked_inputs = false;
|
||||
bool has_available_linked_outputs = false;
|
||||
bNodeTree *owner_tree = nullptr;
|
||||
};
|
||||
|
||||
@@ -269,10 +269,10 @@ inline blender::Span<bNode *> bNodeTree::group_nodes()
|
||||
return this->runtime->group_nodes;
|
||||
}
|
||||
|
||||
inline bool bNodeTree::has_link_cycle() const
|
||||
inline bool bNodeTree::has_available_link_cycle() const
|
||||
{
|
||||
BLI_assert(blender::bke::node_tree_runtime::topology_cache_is_available(*this));
|
||||
return this->runtime->has_link_cycle;
|
||||
return this->runtime->has_available_link_cycle;
|
||||
}
|
||||
|
||||
inline bool bNodeTree::has_undefined_nodes_or_sockets() const
|
||||
@@ -455,6 +455,11 @@ inline bool bNodeLink::is_muted() const
|
||||
return this->flag & NODE_LINK_MUTED;
|
||||
}
|
||||
|
||||
inline bool bNodeLink::is_available() const
|
||||
{
|
||||
return this->fromsock->is_available() && this->tosock->is_available();
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
@@ -123,15 +123,17 @@ static void update_directly_linked_links_and_sockets(const bNodeTree &ntree)
|
||||
socket->runtime->directly_linked_links.clear();
|
||||
socket->runtime->directly_linked_sockets.clear();
|
||||
}
|
||||
node->runtime->has_linked_inputs = false;
|
||||
node->runtime->has_linked_outputs = false;
|
||||
node->runtime->has_available_linked_inputs = false;
|
||||
node->runtime->has_available_linked_outputs = false;
|
||||
}
|
||||
for (bNodeLink *link : tree_runtime.links) {
|
||||
link->fromsock->runtime->directly_linked_links.append(link);
|
||||
link->fromsock->runtime->directly_linked_sockets.append(link->tosock);
|
||||
link->tosock->runtime->directly_linked_links.append(link);
|
||||
link->fromnode->runtime->has_linked_outputs = true;
|
||||
link->tonode->runtime->has_linked_inputs = true;
|
||||
if (link->is_available()) {
|
||||
link->fromnode->runtime->has_available_linked_outputs = true;
|
||||
link->tonode->runtime->has_available_linked_inputs = true;
|
||||
}
|
||||
}
|
||||
for (bNodeSocket *socket : tree_runtime.input_sockets) {
|
||||
if (socket->flag & SOCK_MULTI_INPUT) {
|
||||
@@ -168,7 +170,10 @@ static void find_logical_origins_for_socket_recursive(
|
||||
links_to_check = links_to_check.take_front(1);
|
||||
}
|
||||
for (bNodeLink *link : links_to_check) {
|
||||
if (link->flag & NODE_LINK_MUTED) {
|
||||
if (link->is_muted()) {
|
||||
continue;
|
||||
}
|
||||
if (!link->is_available()) {
|
||||
continue;
|
||||
}
|
||||
bNodeSocket &origin_socket = *link->fromsock;
|
||||
@@ -285,14 +290,20 @@ static void toposort_from_start_node(const ToposortDirection direction,
|
||||
break;
|
||||
}
|
||||
bNodeSocket &socket = *sockets[item.socket_index];
|
||||
const Span<bNodeSocket *> linked_sockets = socket.runtime->directly_linked_sockets;
|
||||
if (item.link_index == linked_sockets.size()) {
|
||||
const Span<bNodeLink *> linked_links = socket.runtime->directly_linked_links;
|
||||
if (item.link_index == linked_links.size()) {
|
||||
/* All links connected to this socket have already been visited. */
|
||||
item.socket_index++;
|
||||
item.link_index = 0;
|
||||
continue;
|
||||
}
|
||||
bNodeSocket &linked_socket = *linked_sockets[item.link_index];
|
||||
bNodeLink &link = *linked_links[item.link_index];
|
||||
if (!link.is_available()) {
|
||||
/* Ignore unavailable links. */
|
||||
item.link_index++;
|
||||
continue;
|
||||
}
|
||||
bNodeSocket &linked_socket = *socket.runtime->directly_linked_sockets[item.link_index];
|
||||
bNode &linked_node = *linked_socket.runtime->owner_node;
|
||||
ToposortNodeState &linked_node_state = node_states[linked_node.runtime->index_in_tree];
|
||||
if (linked_node_state.is_done) {
|
||||
@@ -337,8 +348,9 @@ static void update_toposort(const bNodeTree &ntree,
|
||||
/* Ignore nodes that are done already. */
|
||||
continue;
|
||||
}
|
||||
if ((direction == ToposortDirection::LeftToRight) ? node->runtime->has_linked_outputs :
|
||||
node->runtime->has_linked_inputs) {
|
||||
if ((direction == ToposortDirection::LeftToRight) ?
|
||||
node->runtime->has_available_linked_outputs :
|
||||
node->runtime->has_available_linked_inputs) {
|
||||
/* Ignore non-start nodes. */
|
||||
continue;
|
||||
}
|
||||
@@ -398,7 +410,7 @@ static void ensure_topology_cache(const bNodeTree &ntree)
|
||||
update_toposort(ntree,
|
||||
ToposortDirection::LeftToRight,
|
||||
tree_runtime.toposort_left_to_right,
|
||||
tree_runtime.has_link_cycle);
|
||||
tree_runtime.has_available_link_cycle);
|
||||
},
|
||||
[&]() {
|
||||
bool dummy;
|
||||
|
||||
@@ -1388,7 +1388,7 @@ class NodeTreeMainUpdater {
|
||||
uint32_t get_combined_socket_topology_hash(const bNodeTree &tree,
|
||||
Span<const bNodeSocket *> sockets)
|
||||
{
|
||||
if (tree.has_link_cycle()) {
|
||||
if (tree.has_available_link_cycle()) {
|
||||
/* Return dummy value when the link has any cycles. The algorithm below could be improved to
|
||||
* handle cycles more gracefully. */
|
||||
return 0;
|
||||
@@ -1404,7 +1404,7 @@ class NodeTreeMainUpdater {
|
||||
Array<uint32_t> get_socket_topology_hashes(const bNodeTree &tree,
|
||||
Span<const bNodeSocket *> sockets)
|
||||
{
|
||||
BLI_assert(!tree.has_link_cycle());
|
||||
BLI_assert(!tree.has_available_link_cycle());
|
||||
Array<std::optional<uint32_t>> hash_by_socket_id(tree.all_sockets().size());
|
||||
Stack<const bNodeSocket *> sockets_to_check = sockets;
|
||||
|
||||
|
||||
@@ -505,6 +505,7 @@ typedef struct bNodeLink {
|
||||
|
||||
#ifdef __cplusplus
|
||||
bool is_muted() const;
|
||||
bool is_available() const;
|
||||
#endif
|
||||
|
||||
} bNodeLink;
|
||||
@@ -655,12 +656,12 @@ typedef struct bNodeTree {
|
||||
/**
|
||||
* Cached toposort of all nodes. If there are cycles, the returned array is not actually a
|
||||
* toposort. However, if a connected component does not contain a cycle, this component is sorted
|
||||
* correctly. Use #has_link_cycle to check for cycles.
|
||||
* correctly. Use #has_available_link_cycle to check for cycles.
|
||||
*/
|
||||
blender::Span<const bNode *> toposort_left_to_right() const;
|
||||
blender::Span<const bNode *> toposort_right_to_left() const;
|
||||
/** True when there are any cycles in the node tree. */
|
||||
bool has_link_cycle() const;
|
||||
bool has_available_link_cycle() const;
|
||||
/**
|
||||
* True when there are nodes or sockets in the node tree that don't use a known type. This can
|
||||
* happen when nodes don't exist in the current Blender version that existed in the version where
|
||||
|
||||
@@ -58,7 +58,7 @@ void DerivedNodeTree::destruct_context_recursively(DTreeContext *context)
|
||||
bool DerivedNodeTree::has_link_cycles() const
|
||||
{
|
||||
for (const bNodeTree *btree : used_btrees_) {
|
||||
if (btree->has_link_cycle()) {
|
||||
if (btree->has_available_link_cycle()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1044,10 +1044,10 @@ struct GeometryNodesLazyFunctionGraphBuilder {
|
||||
if (link->is_muted()) {
|
||||
continue;
|
||||
}
|
||||
const bNodeSocket &to_bsocket = *link->tosock;
|
||||
if (!to_bsocket.is_available()) {
|
||||
if (!link->is_available()) {
|
||||
continue;
|
||||
}
|
||||
const bNodeSocket &to_bsocket = *link->tosock;
|
||||
const CPPType *to_type = get_socket_cpp_type(to_bsocket);
|
||||
if (to_type == nullptr) {
|
||||
continue;
|
||||
@@ -1258,7 +1258,7 @@ const GeometryNodesLazyFunctionGraphInfo *ensure_geometry_nodes_lazy_function_gr
|
||||
const bNodeTree &btree)
|
||||
{
|
||||
btree.ensure_topology_cache();
|
||||
if (btree.has_link_cycle()) {
|
||||
if (btree.has_available_link_cycle()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user