using System.Collections.Generic; using System.Linq; namespace AibisDream.FrameAnimation { internal sealed class ResolvedPlaybackStep { public FrameClip Clip { get; } public string NodeId { get; } public float PlaybackSpeed { get; } public FrameClipEndBehavior TerminalEndBehavior { get; } public ResolvedPlaybackStep( FrameClip clip, string nodeId, float playbackSpeed, FrameClipEndBehavior terminalEndBehavior) { Clip = clip; NodeId = nodeId ?? string.Empty; PlaybackSpeed = playbackSpeed; TerminalEndBehavior = terminalEndBehavior; } } internal sealed class ResolvedPlaybackPlan { public string PlayableId { get; } public bool IsFlow { get; } public IReadOnlyList Steps { get; } public AnimationFlowAsyncCompletionMode AsyncCompletionMode { get; } public ResolvedPlaybackPlan( string playableId, bool isFlow, IReadOnlyList steps, AnimationFlowAsyncCompletionMode asyncCompletionMode = AnimationFlowAsyncCompletionMode.WaitForTerminalLoopFirstCycle) { PlayableId = playableId; IsFlow = isFlow; Steps = steps; AsyncCompletionMode = asyncCompletionMode; } } internal static class FrameAnimationResolver { internal static bool TryResolveNode( FrameAnimationGraph graph, AnimationNode node, out ResolvedPlaybackPlan plan, out FrameAnimationPlaybackError error) { plan = null; error = FrameAnimationPlaybackError.None; if (graph == null) { error = Error(FrameAnimationPlaybackErrorCode.GraphMissing, "FrameAnimationGraph 为空。"); return false; } if (node == null || !graph.Nodes.Contains(node) || node.Type != AnimationNodeType.Clip) { error = Error(FrameAnimationPlaybackErrorCode.InvalidPlayableData, "AnimationNode 不属于当前 Graph 或类型无效。"); return false; } var clips = graph.Clips.Where(clip => clip != null && clip.Id == node.ClipId).ToArray(); if (clips.Length != 1) { error = Error( FrameAnimationPlaybackErrorCode.InvalidPlayableData, $"Node '{node.InternalId}' 的 clipId '{node.ClipId}' 不存在或不唯一。"); return false; } var clip = clips[0]; if (!TryValidateClip(clip, out error)) { return false; } var speed = node.SpeedOverride ?? clip.Speed; if (!FrameAnimationValueUtility.IsValidSpeed(speed)) { error = Error(FrameAnimationPlaybackErrorCode.InvalidSpeed, $"Node '{node.InternalId}' 的有效速度无效。"); return false; } plan = new ResolvedPlaybackPlan( node.InternalId, false, new[] { new ResolvedPlaybackStep( clip, node.InternalId, speed, node.EndBehaviorOverride ?? clip.DefaultEndBehavior) }); return true; } public static bool TryResolve( FrameAnimationGraph graph, string playableId, FrameAnimationPlayOptions options, out ResolvedPlaybackPlan plan, out FrameAnimationPlaybackError error) { plan = null; error = FrameAnimationPlaybackError.None; if (graph == null) { error = Error(FrameAnimationPlaybackErrorCode.GraphMissing, "FrameAnimationGraph 为空。"); return false; } if (string.IsNullOrWhiteSpace(playableId)) { error = Error(FrameAnimationPlaybackErrorCode.PlayableIdEmpty, "playableId 不能为空。"); return false; } var clipMatches = graph.Clips.Where(clip => clip != null && clip.Id == playableId).ToList(); var flowMatches = graph.Flows.Where(flow => flow != null && flow.Id == playableId).ToList(); var matchCount = clipMatches.Count + flowMatches.Count; if (matchCount == 0) { error = Error( FrameAnimationPlaybackErrorCode.PlayableNotFound, $"Graph '{graph.Id}' 中不存在 playable '{playableId}'。"); return false; } if (matchCount > 1) { error = Error( FrameAnimationPlaybackErrorCode.InvalidPlayableData, $"playableId '{playableId}' 在 Clip / Flow 统一命名空间中不唯一。"); return false; } if (clipMatches.Count == 1) { return TryResolveClip(clipMatches[0], options, out plan, out error); } return TryResolveFlow(graph, flowMatches[0], options, out plan, out error); } internal static bool TryResolveClip( FrameClip clip, FrameAnimationPlayOptions options, out ResolvedPlaybackPlan plan, out FrameAnimationPlaybackError error) { plan = null; if (!TryValidateClip(clip, out error)) { return false; } var endBehavior = options.EndBehaviorOverride ?? clip.DefaultEndBehavior; plan = new ResolvedPlaybackPlan( clip != null ? clip.Id : string.Empty, false, new[] { new ResolvedPlaybackStep(clip, string.Empty, clip.Speed, endBehavior) }); return true; } private static bool TryResolveFlow( FrameAnimationGraph graph, AnimationFlow flow, FrameAnimationPlayOptions options, out ResolvedPlaybackPlan plan, out FrameAnimationPlaybackError error) { plan = null; error = FrameAnimationPlaybackError.None; var steps = new List(); var visited = new HashSet(); var currentNodeId = flow.EntryNodeId; var topology = new FrameAnimationGraphTopology(graph); while (true) { if (!FrameAnimationValueUtility.IsValidInternalId(currentNodeId)) { error = Error( FrameAnimationPlaybackErrorCode.InvalidPlayableData, $"Flow '{flow.Id}' 的入口或后继 Node internalId 无效。"); return false; } if (!visited.Add(currentNodeId)) { error = Error( FrameAnimationPlaybackErrorCode.InvalidPlayableData, $"Flow '{flow.Id}' 的可达路径形成了环路。"); return false; } var nodeMatches = topology.FindNodes(currentNodeId); if (nodeMatches.Count != 1) { error = Error( FrameAnimationPlaybackErrorCode.InvalidPlayableData, $"Flow '{flow.Id}' 的 Node '{currentNodeId}' 不存在或不唯一。"); return false; } var node = nodeMatches[0]; if (node.Type != AnimationNodeType.Clip) { error = Error( FrameAnimationPlaybackErrorCode.InvalidPlayableData, $"Node '{node.InternalId}' 使用了第一版不支持的节点类型。"); return false; } var clipMatches = graph.Clips.Where(clip => clip != null && clip.Id == node.ClipId).ToList(); if (clipMatches.Count != 1) { error = Error( FrameAnimationPlaybackErrorCode.InvalidPlayableData, $"Node '{node.InternalId}' 的 clipId '{node.ClipId}' 不存在或不唯一。"); return false; } var clip = clipMatches[0]; if (!TryValidateClip(clip, out error)) { return false; } var nodeSpeed = node.SpeedOverride ?? clip.Speed; if (!FrameAnimationValueUtility.IsValidSpeed(nodeSpeed)) { error = Error( FrameAnimationPlaybackErrorCode.InvalidSpeed, $"Node '{node.InternalId}' 的有效播放速度无效。"); return false; } var outgoing = topology.GetOutgoing(currentNodeId); if (outgoing.Count > 1) { error = Error( FrameAnimationPlaybackErrorCode.InvalidPlayableData, $"Node '{node.InternalId}' 第一版最多只能有一个后继 Edge。"); return false; } if (node.EndBehaviorOverride.HasValue && outgoing.Count > 0) { error = Error( FrameAnimationPlaybackErrorCode.InvalidPlayableData, $"Node '{node.InternalId}' 已设置结束行为,不能同时拥有后继 Edge。"); return false; } var isTerminal = outgoing.Count == 0; var terminalBehavior = node.EndBehaviorOverride ?? options.EndBehaviorOverride ?? flow.EndBehaviorOverride ?? clip.DefaultEndBehavior; steps.Add(new ResolvedPlaybackStep(clip, node.InternalId, nodeSpeed, terminalBehavior)); if (isTerminal) { break; } var edge = outgoing[0]; if (!FrameAnimationValueUtility.IsValidInternalId(edge.InternalId) || edge.Condition != AnimationEdgeCondition.Always || edge.FromNodeId == edge.ToNodeId) { error = Error( FrameAnimationPlaybackErrorCode.InvalidPlayableData, $"Node '{node.InternalId}' 的后继 Edge 无效。"); return false; } currentNodeId = edge.ToNodeId; } plan = new ResolvedPlaybackPlan( flow.Id, true, steps, flow.AsyncCompletionMode); return true; } private static bool TryValidateClip(FrameClip clip, out FrameAnimationPlaybackError error) { if (clip == null || string.IsNullOrWhiteSpace(clip.Id) || clip.FrameCount == 0) { error = Error( FrameAnimationPlaybackErrorCode.InvalidPlayableData, "FrameClip 为空、id 为空或没有 Frame。"); return false; } if (!FrameAnimationValueUtility.IsValidSpeed(clip.Speed)) { error = Error( FrameAnimationPlaybackErrorCode.InvalidSpeed, $"Clip '{clip.Id}' 的 speed 无效。"); return false; } for (var index = 0; index < clip.Frames.Count; index++) { var frame = clip.Frames[index]; if (frame == null || frame.DurationMs <= 0) { error = Error( FrameAnimationPlaybackErrorCode.InvalidPlayableData, $"Clip '{clip.Id}' 的第 {index} 帧为空或 durationMs 无效。"); return false; } } error = FrameAnimationPlaybackError.None; return true; } private static FrameAnimationPlaybackError Error( FrameAnimationPlaybackErrorCode code, string message) { return new FrameAnimationPlaybackError(code, message); } } }