mirror of
https://github.com/4sval/FModel.git
synced 2026-06-22 07:50:10 -05:00
Fix SaveCachedPose to be placed in correct ancestor _Root layer
Instead of always placing SaveCachedPose in the primary (first) _Root layer, trace downstream UseCachedPose consumers through the state machine hierarchy (BelongsToStateMachine → StateMachineName) to find the correct ancestor animation blueprint layer. For example, a SaveCachedPose used by UseCachedPose in AnimGraph > BaseLayer > LocomotionStates > IdleState is now correctly placed in BaseLayer (the _Root layer containing LocomotionStates) instead of AnimGraph. - Add FindOwnerRootLayer: traces SaveCachedPose downstream consumers to find the correct _Root layer - Add GetAncestorRootLayer: walks up the layer hierarchy via BelongsToStateMachine → StateMachineName chain - Add BuildLayerLookups/LayerLookups: pre-computed lookup maps for efficient multi-node queries - Update EnforceSaveCachedPoseInRootLayers to use smart layer detection Co-authored-by: LoogLong <86428208+LoogLong@users.noreply.github.com>
This commit is contained in:
parent
99aa9d518f
commit
87cb948dfb
|
|
@ -231,9 +231,10 @@ public class AnimGraphViewModel
|
|||
AddStateSubGraph(vm, layerNodes, stateResultNode.Name, layerIndex++);
|
||||
}
|
||||
|
||||
// Pass 3: Catch any still-unassigned SaveCachedPose nodes and their input
|
||||
// chains, adding them to the primary AnimGraph layer before the fallback
|
||||
// handles remaining nodes.
|
||||
// Pass 3: Assign unassigned SaveCachedPose nodes to their correct _Root layer.
|
||||
// SaveCachedPose can only exist in animation blueprint layers (defined by _Root nodes).
|
||||
// Determine the correct _Root layer by tracing downstream UseCachedPose consumers
|
||||
// back through the state machine hierarchy to their parent animation blueprint layer.
|
||||
if (primaryGraphLayer != null)
|
||||
{
|
||||
var unassignedSavePoseNodes = vm.Nodes
|
||||
|
|
@ -242,14 +243,20 @@ public class AnimGraphViewModel
|
|||
|
||||
if (unassignedSavePoseNodes.Count > 0)
|
||||
{
|
||||
var lookups = BuildLayerLookups(vm);
|
||||
var affectedLayers = new HashSet<AnimGraphLayer>();
|
||||
|
||||
foreach (var saveNode in unassignedSavePoseNodes)
|
||||
{
|
||||
if (!assigned.Add(saveNode)) continue;
|
||||
var targetLayer = FindOwnerRootLayer(saveNode, vm, lookups) ?? primaryGraphLayer;
|
||||
var inputChain = CollectUpstream(saveNode, upstreamOf, assigned);
|
||||
primaryGraphLayer.Nodes.AddRange(inputChain);
|
||||
targetLayer.Nodes.AddRange(inputChain);
|
||||
affectedLayers.Add(targetLayer);
|
||||
}
|
||||
|
||||
RebuildLayerConnections(vm, primaryGraphLayer);
|
||||
foreach (var layer in affectedLayers)
|
||||
RebuildLayerConnections(vm, layer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -298,24 +305,23 @@ public class AnimGraphViewModel
|
|||
// Final enforcement: SaveCachedPose nodes can only exist in animation
|
||||
// blueprint layers (layers that contain a _Root node). After all passes
|
||||
// above, scan every non-_Root layer (state sub-graphs and fallback layers)
|
||||
// and move any SaveCachedPose nodes to the primary _Root layer.
|
||||
// and move any SaveCachedPose nodes to the correct _Root layer.
|
||||
if (primaryGraphLayer != null)
|
||||
{
|
||||
EnforceSaveCachedPoseInAnimBlueprintLayer(vm, primaryGraphLayer);
|
||||
EnforceSaveCachedPoseInRootLayers(vm, primaryGraphLayer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures every SaveCachedPose node resides in an animation blueprint layer
|
||||
/// (one that contains a _Root node). Any SaveCachedPose found in a state
|
||||
/// sub-graph or a fallback connected-component layer is moved to
|
||||
/// <paramref name="targetLayer"/> and the affected layers are rebuilt.
|
||||
/// sub-graph or a fallback connected-component layer is moved to the correct
|
||||
/// _Root layer determined by tracing its UseCachedPose consumers.
|
||||
/// Falls back to <paramref name="fallbackLayer"/> if no better target is found.
|
||||
/// </summary>
|
||||
private static void EnforceSaveCachedPoseInAnimBlueprintLayer(
|
||||
AnimGraphViewModel vm, AnimGraphLayer targetLayer)
|
||||
private static void EnforceSaveCachedPoseInRootLayers(
|
||||
AnimGraphViewModel vm, AnimGraphLayer fallbackLayer)
|
||||
{
|
||||
var moved = false;
|
||||
|
||||
// Identify animation-blueprint layers (layers that contain a _Root node)
|
||||
var animBlueprintLayers = new HashSet<AnimGraphLayer>();
|
||||
foreach (var layer in vm.Layers)
|
||||
|
|
@ -324,46 +330,140 @@ public class AnimGraphViewModel
|
|||
animBlueprintLayers.Add(layer);
|
||||
}
|
||||
|
||||
// Scan non-animation-blueprint layers in vm.Layers
|
||||
var affectedLayers = new HashSet<AnimGraphLayer>();
|
||||
// Collect SaveCachedPose nodes from non-_Root layers and state sub-graphs
|
||||
var moves = new List<(AnimGraphNode node, AnimGraphLayer sourceLayer)>();
|
||||
|
||||
foreach (var layer in vm.Layers)
|
||||
{
|
||||
if (animBlueprintLayers.Contains(layer)) continue;
|
||||
var toMove = layer.Nodes.Where(IsSaveCachedPoseNode).ToList();
|
||||
if (toMove.Count == 0) continue;
|
||||
|
||||
foreach (var node in toMove)
|
||||
{
|
||||
layer.Nodes.Remove(node);
|
||||
targetLayer.Nodes.Add(node);
|
||||
}
|
||||
affectedLayers.Add(layer);
|
||||
moved = true;
|
||||
foreach (var node in layer.Nodes.Where(IsSaveCachedPoseNode).ToList())
|
||||
moves.Add((node, layer));
|
||||
}
|
||||
|
||||
// Scan state sub-graphs
|
||||
foreach (var (_, layer) in vm.StateSubGraphs)
|
||||
{
|
||||
var toMove = layer.Nodes.Where(IsSaveCachedPoseNode).ToList();
|
||||
if (toMove.Count == 0) continue;
|
||||
|
||||
foreach (var node in toMove)
|
||||
{
|
||||
layer.Nodes.Remove(node);
|
||||
targetLayer.Nodes.Add(node);
|
||||
}
|
||||
affectedLayers.Add(layer);
|
||||
moved = true;
|
||||
foreach (var node in layer.Nodes.Where(IsSaveCachedPoseNode).ToList())
|
||||
moves.Add((node, layer));
|
||||
}
|
||||
|
||||
if (!moved) return;
|
||||
if (moves.Count == 0) return;
|
||||
|
||||
var lookups = BuildLayerLookups(vm);
|
||||
var affectedLayers = new HashSet<AnimGraphLayer>();
|
||||
foreach (var (node, sourceLayer) in moves)
|
||||
{
|
||||
var targetLayer = FindOwnerRootLayer(node, vm, lookups) ?? fallbackLayer;
|
||||
sourceLayer.Nodes.Remove(node);
|
||||
targetLayer.Nodes.Add(node);
|
||||
affectedLayers.Add(sourceLayer);
|
||||
affectedLayers.Add(targetLayer);
|
||||
}
|
||||
|
||||
// Rebuild connections for the target layer and all affected source layers
|
||||
RebuildLayerConnections(vm, targetLayer);
|
||||
foreach (var layer in affectedLayers)
|
||||
RebuildLayerConnections(vm, layer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pre-computed lookup maps for layer hierarchy traversal.
|
||||
/// </summary>
|
||||
private readonly struct LayerLookups(
|
||||
Dictionary<AnimGraphNode, AnimGraphLayer> nodeToLayer,
|
||||
Dictionary<string, AnimGraphLayer> machineToLayer)
|
||||
{
|
||||
public Dictionary<AnimGraphNode, AnimGraphLayer> NodeToLayer { get; } = nodeToLayer;
|
||||
public Dictionary<string, AnimGraphLayer> MachineToLayer { get; } = machineToLayer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the lookup maps needed by <see cref="FindOwnerRootLayer"/> and
|
||||
/// <see cref="GetAncestorRootLayer"/>. Call once and reuse for multiple queries.
|
||||
/// </summary>
|
||||
private static LayerLookups BuildLayerLookups(AnimGraphViewModel vm)
|
||||
{
|
||||
var nodeToLayer = new Dictionary<AnimGraphNode, AnimGraphLayer>();
|
||||
foreach (var layer in vm.Layers)
|
||||
foreach (var node in layer.Nodes)
|
||||
nodeToLayer.TryAdd(node, layer);
|
||||
foreach (var (_, subGraph) in vm.StateSubGraphs)
|
||||
foreach (var node in subGraph.Nodes)
|
||||
nodeToLayer.TryAdd(node, subGraph);
|
||||
|
||||
var machineToLayer = new Dictionary<string, AnimGraphLayer>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var layer in vm.Layers)
|
||||
foreach (var node in layer.Nodes)
|
||||
if (node.AdditionalProperties.TryGetValue("StateMachineName", out var mn))
|
||||
machineToLayer.TryAdd(mn, layer);
|
||||
foreach (var (_, subGraph) in vm.StateSubGraphs)
|
||||
foreach (var node in subGraph.Nodes)
|
||||
if (node.AdditionalProperties.TryGetValue("StateMachineName", out var mn))
|
||||
machineToLayer.TryAdd(mn, subGraph);
|
||||
|
||||
return new LayerLookups(nodeToLayer, machineToLayer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the correct animation blueprint layer (_Root layer) for a SaveCachedPose node
|
||||
/// by tracing its downstream UseCachedPose consumers through the state machine
|
||||
/// hierarchy to find the parent animation blueprint layer.
|
||||
/// Returns null if no suitable layer is found.
|
||||
/// </summary>
|
||||
private static AnimGraphLayer? FindOwnerRootLayer(
|
||||
AnimGraphNode saveNode, AnimGraphViewModel vm, LayerLookups lookups)
|
||||
{
|
||||
// Find downstream consumers (UseCachedPose nodes referencing this SaveCachedPose)
|
||||
// Trace each consumer up to find the ancestor _Root layer
|
||||
foreach (var conn in vm.Connections)
|
||||
{
|
||||
if (conn.SourceNode != saveNode) continue;
|
||||
|
||||
if (!lookups.NodeToLayer.TryGetValue(conn.TargetNode, out var consumerLayer))
|
||||
continue;
|
||||
|
||||
var rootLayer = GetAncestorRootLayer(consumerLayer, lookups.MachineToLayer);
|
||||
if (rootLayer != null)
|
||||
return rootLayer;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Walks up the layer hierarchy to find the nearest ancestor animation blueprint
|
||||
/// layer (one containing a _Root node). For state sub-graphs, traces through the
|
||||
/// BelongsToStateMachine → StateMachineName chain to find the parent layer.
|
||||
/// </summary>
|
||||
private static AnimGraphLayer? GetAncestorRootLayer(
|
||||
AnimGraphLayer layer,
|
||||
Dictionary<string, AnimGraphLayer> machineToLayer)
|
||||
{
|
||||
var visited = new HashSet<AnimGraphLayer>();
|
||||
var current = layer;
|
||||
|
||||
while (current != null && visited.Add(current))
|
||||
{
|
||||
// If this layer contains a _Root node, it's an animation blueprint layer
|
||||
if (current.Nodes.Any(n => n.ExportType.EndsWith("_Root", StringComparison.OrdinalIgnoreCase)))
|
||||
return current;
|
||||
|
||||
// Find the state machine this sub-graph belongs to
|
||||
string? smName = null;
|
||||
foreach (var node in current.Nodes)
|
||||
{
|
||||
if (node.AdditionalProperties.TryGetValue("BelongsToStateMachine", out var val) &&
|
||||
!string.IsNullOrEmpty(val))
|
||||
{
|
||||
smName = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(smName) || !machineToLayer.TryGetValue(smName, out current))
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds a layer's connection list from vm.Connections based on which
|
||||
/// nodes are currently in the layer, then re-runs layout.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user