From 6620463154fcff101cd89f124457437cd9814dbd Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 3 Mar 2026 13:35:18 +0000
Subject: [PATCH] Separate _Root and _StateResult into independent passes in
BuildLayers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Split BuildLayers into two distinct passes:
- Pass 1: AnimGraphNode_Root nodes → graph layers
- Pass 2: AnimGraphNode_StateResult nodes → state sub-graphs
Previously these were incorrectly mixed in a single loop. Each type
defines a fundamentally different concept in UE and must be processed
independently. Extracted CollectUpstream helper to share BFS logic.
Co-authored-by: LoogLong <86428208+LoogLong@users.noreply.github.com>
---
FModel/ViewModels/AnimGraphViewModel.cs | 89 +++++++++++++++----------
1 file changed, 55 insertions(+), 34 deletions(-)
diff --git a/FModel/ViewModels/AnimGraphViewModel.cs b/FModel/ViewModels/AnimGraphViewModel.cs
index c0a1d50b..e4e8cda1 100644
--- a/FModel/ViewModels/AnimGraphViewModel.cs
+++ b/FModel/ViewModels/AnimGraphViewModel.cs
@@ -166,11 +166,11 @@ public class AnimGraphViewModel
}
///
- /// Groups nodes into layers based on root nodes. In Unreal Engine, each
- /// animation blueprint layer has a unique AnimGraphNode_Root, and each
- /// state machine state sub-graph has a unique AnimGraphNode_StateResult.
- /// Starting from each root node, we trace upstream through directed
- /// connections to collect all nodes that feed into it.
+ /// Groups nodes into layers based on root nodes. In Unreal Engine:
+ /// - Each animation blueprint layer has a unique AnimGraphNode_Root
+ /// - Each state machine state sub-graph has a unique AnimGraphNode_StateResult
+ /// These two types are processed in separate passes to keep graph layers
+ /// and state machine state sub-graphs independent.
///
private static void BuildLayers(AnimGraphViewModel vm)
{
@@ -185,43 +185,36 @@ public class AnimGraphViewModel
foreach (var conn in vm.Connections)
upstreamOf[conn.TargetNode].Add(conn.SourceNode);
- // Find all root nodes that define layers:
- // _Root nodes define animation blueprint layers
- // _StateResult nodes define state machine state sub-graphs
- var rootNodes = vm.Nodes
- .Where(n => n.ExportType.EndsWith("_Root", StringComparison.OrdinalIgnoreCase) ||
- n.ExportType.EndsWith("_StateResult", StringComparison.OrdinalIgnoreCase))
- .ToList();
-
var assigned = new HashSet();
var layerIndex = 0;
- // For each root node, BFS upstream to find all nodes in the layer
- foreach (var rootNode in rootNodes)
+ // Pass 1: Build graph layers from AnimGraphNode_Root nodes.
+ // Each _Root node defines an animation blueprint layer (e.g. "AnimGraph").
+ var graphRoots = vm.Nodes
+ .Where(n => n.ExportType.EndsWith("_Root", StringComparison.OrdinalIgnoreCase))
+ .ToList();
+
+ foreach (var rootNode in graphRoots)
{
if (!assigned.Add(rootNode)) continue;
-
- var layerNodes = new List { rootNode };
- var queue = new Queue();
- queue.Enqueue(rootNode);
-
- while (queue.Count > 0)
- {
- var current = queue.Dequeue();
- foreach (var upstream in upstreamOf[current])
- {
- if (assigned.Add(upstream))
- {
- layerNodes.Add(upstream);
- queue.Enqueue(upstream);
- }
- }
- }
-
+ var layerNodes = CollectUpstream(rootNode, upstreamOf, assigned);
AddLayer(vm, layerNodes, layerIndex++);
}
- // Any remaining unassigned nodes go into fallback layers (connected components)
+ // Pass 2: Build state machine state sub-graphs from AnimGraphNode_StateResult nodes.
+ // Each _StateResult node defines a state's sub-graph within a state machine.
+ var stateResultRoots = vm.Nodes
+ .Where(n => n.ExportType.EndsWith("_StateResult", StringComparison.OrdinalIgnoreCase))
+ .ToList();
+
+ foreach (var stateResultNode in stateResultRoots)
+ {
+ if (!assigned.Add(stateResultNode)) continue;
+ var layerNodes = CollectUpstream(stateResultNode, upstreamOf, assigned);
+ AddLayer(vm, layerNodes, layerIndex++);
+ }
+
+ // Fallback: any remaining unassigned nodes go into connected-component layers
var remaining = vm.Nodes.Where(n => !assigned.Contains(n)).ToList();
if (remaining.Count == 0) return;
@@ -263,6 +256,34 @@ public class AnimGraphViewModel
}
}
+ ///
+ /// Collects a root node and all its upstream providers via BFS.
+ ///
+ private static List CollectUpstream(
+ AnimGraphNode root,
+ Dictionary> upstreamOf,
+ HashSet assigned)
+ {
+ var nodes = new List { root };
+ var queue = new Queue();
+ queue.Enqueue(root);
+
+ while (queue.Count > 0)
+ {
+ var current = queue.Dequeue();
+ foreach (var upstream in upstreamOf[current])
+ {
+ if (assigned.Add(upstream))
+ {
+ nodes.Add(upstream);
+ queue.Enqueue(upstream);
+ }
+ }
+ }
+
+ return nodes;
+ }
+
///
/// Creates an from a set of nodes, assigns
/// the relevant connections, lays out the nodes and adds the layer to the VM.