using System; using System.Collections.Generic; using System.Linq; namespace AibisDream.FrameAnimation { internal sealed class FrameAnimationReachability { public IReadOnlyList Nodes { get; } public IReadOnlyList Edges { get; } internal FrameAnimationReachability( IReadOnlyList nodes, IReadOnlyList edges) { Nodes = nodes ?? Array.Empty(); Edges = edges ?? Array.Empty(); } } internal sealed class FrameAnimationGraphTopology { private readonly FrameAnimationGraph graph; private readonly Dictionary> nodesById; private readonly Dictionary> outgoingByNodeId; internal FrameAnimationGraphTopology(FrameAnimationGraph graph) { this.graph = graph; nodesById = (graph?.Nodes ?? Array.Empty()) .Where(node => node != null) .GroupBy(node => node.InternalId ?? string.Empty) .ToDictionary(group => group.Key, group => group.ToList()); outgoingByNodeId = (graph?.Edges ?? Array.Empty()) .Where(edge => edge != null) .GroupBy(edge => edge.FromNodeId ?? string.Empty) .ToDictionary(group => group.Key, group => group.ToList()); } internal IReadOnlyList FindNodes(string internalId) { return nodesById.TryGetValue(internalId ?? string.Empty, out var nodes) ? nodes : Array.Empty(); } internal IReadOnlyList GetOutgoing(string nodeId) { return outgoingByNodeId.TryGetValue(nodeId ?? string.Empty, out var edges) ? edges : Array.Empty(); } internal FrameAnimationReachability GetReachable(string entryNodeId) { var nodes = new List(); var edges = new List(); var visitedNodes = new HashSet(); var visitedEdges = new HashSet(); var queue = new Queue(); 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(); var stack = new Stack(); 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 FindFlowsUsingNode(string nodeId) { if (graph == null) { return Array.Empty(); } 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 FindFlowsUsingEdge(string edgeId) { if (graph == null) { return Array.Empty(); } 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(); } } }