diff --git a/cpp/functions/gfx/dag_node_editor.cpp b/cpp/functions/gfx/dag_node_editor.cpp index 0c7538c4..fde8245d 100644 --- a/cpp/functions/gfx/dag_node_editor.cpp +++ b/cpp/functions/gfx/dag_node_editor.cpp @@ -4,6 +4,7 @@ #include "imgui_node_editor.h" #include #include +#include #include #include #include @@ -325,14 +326,38 @@ bool dag_node_editor(std::vector& pipeline) { int from_idx = find_by_uid(pipeline, from_uid); int to_idx = find_by_uid(pipeline, to_uid); - // Reject cycles: in topo-ordered list, from must come before to. - // We also reject self-loops. + // Real cycle check: would the new edge from->to introduce a path + // from `from` back to itself? It does iff `from` already (transitively) + // depends on `to`. We walk source_ids of `from` and reject if we + // ever hit `to`. Vector index order is irrelevant — topo_sort runs + // at end-of-frame and reorders the pipeline. + bool cycle = false; if (from_uid == to_uid) { - ed::RejectNewItem(ImVec4(1, 0, 0, 1)); - valid = false; - } else if (from_idx >= to_idx) { - // Would create a back-edge; reject - ed::RejectNewItem(ImVec4(1, 0.5f, 0, 1)); + cycle = true; + } else if (from_idx >= 0 && to_idx >= 0) { + const std::string to_id = pipeline[static_cast(to_idx)].id; + std::function depends_on_to; + depends_on_to = [&](const std::string& node_id) -> bool { + if (node_id == to_id) return true; + int idx = -1; + for (int i = 0; i < static_cast(pipeline.size()); ++i) { + if (pipeline[static_cast(i)].id == node_id) { idx = i; break; } + } + if (idx < 0) return false; + const DagStep& s = pipeline[static_cast(idx)]; + const DagNodeDef* d = dag_find(s.name); + int n = d ? d->num_inputs : 0; + for (int k = 0; k < n; ++k) { + const std::string& sid = s.source_ids[static_cast(k)]; + if (!sid.empty() && depends_on_to(sid)) return true; + } + return false; + }; + cycle = depends_on_to(pipeline[static_cast(from_idx)].id); + } + + if (cycle) { + ed::RejectNewItem(ImVec4(1, 0.3f, 0.3f, 1)); valid = false; }