Files

143 lines
5.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
namespace AibisDream.FrameAnimation
{
internal sealed class FrameAnimationReachability
{
public IReadOnlyList<AnimationNode> Nodes { get; }
public IReadOnlyList<AnimationEdge> Edges { get; }
internal FrameAnimationReachability(
IReadOnlyList<AnimationNode> nodes,
IReadOnlyList<AnimationEdge> edges)
{
Nodes = nodes ?? Array.Empty<AnimationNode>();
Edges = edges ?? Array.Empty<AnimationEdge>();
}
}
internal sealed class FrameAnimationGraphTopology
{
private readonly FrameAnimationGraph graph;
private readonly Dictionary<string, List<AnimationNode>> nodesById;
private readonly Dictionary<string, List<AnimationEdge>> outgoingByNodeId;
internal FrameAnimationGraphTopology(FrameAnimationGraph graph)
{
this.graph = graph;
nodesById = (graph?.Nodes ?? Array.Empty<AnimationNode>())
.Where(node => node != null)
.GroupBy(node => node.InternalId ?? string.Empty)
.ToDictionary(group => group.Key, group => group.ToList());
outgoingByNodeId = (graph?.Edges ?? Array.Empty<AnimationEdge>())
.Where(edge => edge != null)
.GroupBy(edge => edge.FromNodeId ?? string.Empty)
.ToDictionary(group => group.Key, group => group.ToList());
}
internal IReadOnlyList<AnimationNode> FindNodes(string internalId)
{
return nodesById.TryGetValue(internalId ?? string.Empty, out var nodes)
? nodes
: Array.Empty<AnimationNode>();
}
internal IReadOnlyList<AnimationEdge> GetOutgoing(string nodeId)
{
return outgoingByNodeId.TryGetValue(nodeId ?? string.Empty, out var edges)
? edges
: Array.Empty<AnimationEdge>();
}
internal FrameAnimationReachability GetReachable(string entryNodeId)
{
var nodes = new List<AnimationNode>();
var edges = new List<AnimationEdge>();
var visitedNodes = new HashSet<string>();
var visitedEdges = new HashSet<string>();
var queue = new Queue<string>();
queue.Enqueue(entryNodeId ?? string.Empty);
while (queue.Count > 0)
{
var nodeId = queue.Dequeue();
if (!visitedNodes.Add(nodeId))
{
continue;
}
if (nodesById.TryGetValue(nodeId, out var matches))
{
nodes.AddRange(matches);
}
if (!outgoingByNodeId.TryGetValue(nodeId, out var outgoing))
{
continue;
}
foreach (var edge in outgoing)
{
if (visitedEdges.Add(edge.InternalId ?? string.Empty))
{
edges.Add(edge);
}
queue.Enqueue(edge.ToNodeId ?? string.Empty);
}
}
return new FrameAnimationReachability(nodes, edges);
}
internal bool WouldCreateCycle(string fromNodeId, string toNodeId)
{
if (string.IsNullOrEmpty(fromNodeId) || string.IsNullOrEmpty(toNodeId) || fromNodeId == toNodeId)
{
return true;
}
var visited = new HashSet<string>();
var stack = new Stack<string>();
stack.Push(toNodeId);
while (stack.Count > 0)
{
var current = stack.Pop();
if (!visited.Add(current))
{
continue;
}
if (current == fromNodeId)
{
return true;
}
foreach (var edge in GetOutgoing(current))
{
stack.Push(edge.ToNodeId ?? string.Empty);
}
}
return false;
}
internal IReadOnlyList<AnimationFlow> FindFlowsUsingNode(string nodeId)
{
if (graph == null)
{
return Array.Empty<AnimationFlow>();
}
return graph.Flows.Where(flow => flow != null &&
GetReachable(flow.EntryNodeId).Nodes.Any(node => node != null && node.InternalId == nodeId))
.OrderBy(flow => flow.Id, StringComparer.Ordinal)
.ToArray();
}
internal IReadOnlyList<AnimationFlow> FindFlowsUsingEdge(string edgeId)
{
if (graph == null)
{
return Array.Empty<AnimationFlow>();
}
return graph.Flows.Where(flow => flow != null &&
GetReachable(flow.EntryNodeId).Edges.Any(edge => edge != null && edge.InternalId == edgeId))
.OrderBy(flow => flow.Id, StringComparer.Ordinal)
.ToArray();
}
}
}