feat: FlowAsync调整

This commit is contained in:
2026-08-05 21:14:06 +08:00
parent 9c3c467046
commit 121e7f398c
13 changed files with 513 additions and 59 deletions
@@ -90,12 +90,15 @@ namespace AibisDream.FrameAnimation
[SerializeField] private string id = string.Empty;
[SerializeField] private string displayName = string.Empty;
[SerializeField] private string entryNodeId = string.Empty;
[SerializeField] private AnimationFlowAsyncCompletionMode asyncCompletionMode =
AnimationFlowAsyncCompletionMode.CompleteOnTerminalLoopStart;
[SerializeField] private bool hasEndBehaviorOverride;
[SerializeField] private FrameClipEndBehavior endBehaviorOverride;
public string Id => id;
public string DisplayName => displayName;
public string EntryNodeId => entryNodeId;
public AnimationFlowAsyncCompletionMode AsyncCompletionMode => asyncCompletionMode;
public FrameClipEndBehavior? EndBehaviorOverride =>
hasEndBehaviorOverride ? endBehaviorOverride : (FrameClipEndBehavior?)null;
@@ -107,11 +110,14 @@ namespace AibisDream.FrameAnimation
string id,
string displayName,
string entryNodeId,
FrameClipEndBehavior? endBehaviorOverride = null)
FrameClipEndBehavior? endBehaviorOverride = null,
AnimationFlowAsyncCompletionMode asyncCompletionMode =
AnimationFlowAsyncCompletionMode.CompleteOnTerminalLoopStart)
{
this.id = id ?? string.Empty;
this.displayName = displayName ?? this.id;
this.entryNodeId = entryNodeId ?? string.Empty;
this.asyncCompletionMode = asyncCompletionMode;
SetEndBehaviorOverride(endBehaviorOverride);
}
@@ -135,6 +141,11 @@ namespace AibisDream.FrameAnimation
{
entryNodeId = value ?? string.Empty;
}
internal void SetAsyncCompletionMode(AnimationFlowAsyncCompletionMode value)
{
asyncCompletionMode = value;
}
}
[Serializable]
@@ -5,6 +5,25 @@ using UnityEngine;
namespace AibisDream.FrameAnimation
{
public sealed class FrameAnimationPlaybackAwaitable : CustomYieldInstruction
{
private readonly FrameAnimationPlayer player;
public FrameAnimationPlaybackHandle PlaybackHandle { get; }
internal FrameAnimationPlaybackAwaitable(
FrameAnimationPlayer player,
FrameAnimationPlaybackHandle playbackHandle)
{
this.player = player;
PlaybackHandle = playbackHandle ??
throw new ArgumentNullException(nameof(playbackHandle));
}
public override bool keepWaiting =>
player != null && player.ShouldWaitForAsyncCompletion(PlaybackHandle);
}
public sealed class FrameAnimationPlaybackHandle : CustomYieldInstruction
{
private readonly TaskCompletionSource<FrameAnimationPlaybackResult> completionSource =
@@ -97,6 +97,31 @@ namespace AibisDream.FrameAnimation
public int CurrentFrameIndex => hasStarted && !hasCompleted ? frameIndex : -1;
public FrameClip CurrentClip => CurrentStep?.Clip;
internal bool HasCompletedFirstTerminalLoopCycle => hasCompletedFirstTerminalLoopCycle;
internal bool HasReachedAsyncCompletionPoint
{
get
{
if (!hasStarted || plan.Steps == null || plan.Steps.Count == 0)
{
return false;
}
var terminalStep = plan.Steps[plan.Steps.Count - 1];
if (terminalStep.TerminalEndBehavior != FrameClipEndBehavior.Loop)
{
return hasCompleted;
}
if (!plan.IsFlow ||
plan.AsyncCompletionMode ==
AnimationFlowAsyncCompletionMode.WaitForTerminalLoopFirstCycle)
{
return hasCompletedFirstTerminalLoopCycle;
}
return stepIndex == plan.Steps.Count - 1;
}
}
internal FrameAnimationPlaybackSnapshot Snapshot =>
new FrameAnimationPlaybackSnapshot(
hasStarted,
@@ -138,18 +138,22 @@ namespace AibisDream.FrameAnimation
return PlayInternal(playableId, options);
}
/// <summary>
/// 等待当前播放请求完成首轮。非循环请求等待自然结束;终点为 Loop 的请求在
/// 第一次回绕后结束等待,但播放请求本身保持活动并继续循环。
/// </summary>
public CustomYieldInstruction WaitForFirstPass(FrameAnimationPlaybackHandle playbackHandle)
public FrameAnimationPlaybackAwaitable PlayAsync()
{
if (playbackHandle == null)
{
throw new ArgumentNullException(nameof(playbackHandle));
}
var playableId = graph?.Settings?.DefaultPlayableId ?? string.Empty;
return PlayAsyncInternal(playableId, default);
}
return new FirstPassYieldInstruction(this, playbackHandle);
public FrameAnimationPlaybackAwaitable PlayAsync(string playableId)
{
return PlayAsyncInternal(playableId, default);
}
public FrameAnimationPlaybackAwaitable PlayAsync(
string playableId,
FrameAnimationPlayOptions options)
{
return PlayAsyncInternal(playableId, options);
}
public bool TryBindGraph(
@@ -404,6 +408,15 @@ namespace AibisDream.FrameAnimation
return handle;
}
private FrameAnimationPlaybackAwaitable PlayAsyncInternal(
string playableId,
FrameAnimationPlayOptions options)
{
return new FrameAnimationPlaybackAwaitable(
this,
PlayInternal(playableId, options));
}
private bool TryCreateTarget(
out IFrameAnimationTarget resolvedTarget,
out FrameAnimationPlaybackError error)
@@ -527,31 +540,15 @@ namespace AibisDream.FrameAnimation
private Action<Sprite> ApplySpriteCallback =>
applySpriteCallback ??= ApplySprite;
private bool ShouldWaitForFirstPass(FrameAnimationPlaybackHandle playbackHandle)
internal bool ShouldWaitForAsyncCompletion(
FrameAnimationPlaybackHandle playbackHandle)
{
if (playbackHandle.IsCompleted || activeHandle != playbackHandle || session == null)
{
return false;
}
return !session.HasCompletedFirstTerminalLoopCycle;
}
private sealed class FirstPassYieldInstruction : CustomYieldInstruction
{
private readonly FrameAnimationPlayer player;
private readonly FrameAnimationPlaybackHandle playbackHandle;
public FirstPassYieldInstruction(
FrameAnimationPlayer player,
FrameAnimationPlaybackHandle playbackHandle)
{
this.player = player;
this.playbackHandle = playbackHandle;
}
public override bool keepWaiting =>
player != null && player.ShouldWaitForFirstPass(playbackHandle);
return !session.HasReachedAsyncCompletionPoint;
}
private void ApplyEndBehavior(FrameClipEndBehavior endBehavior)
@@ -28,12 +28,19 @@ namespace AibisDream.FrameAnimation
public string PlayableId { get; }
public bool IsFlow { get; }
public IReadOnlyList<ResolvedPlaybackStep> Steps { get; }
public AnimationFlowAsyncCompletionMode AsyncCompletionMode { get; }
public ResolvedPlaybackPlan(string playableId, bool isFlow, IReadOnlyList<ResolvedPlaybackStep> steps)
public ResolvedPlaybackPlan(
string playableId,
bool isFlow,
IReadOnlyList<ResolvedPlaybackStep> steps,
AnimationFlowAsyncCompletionMode asyncCompletionMode =
AnimationFlowAsyncCompletionMode.WaitForTerminalLoopFirstCycle)
{
PlayableId = playableId;
IsFlow = isFlow;
Steps = steps;
AsyncCompletionMode = asyncCompletionMode;
}
}
@@ -278,7 +285,11 @@ namespace AibisDream.FrameAnimation
currentNodeId = edge.ToNodeId;
}
plan = new ResolvedPlaybackPlan(flow.Id, true, steps);
plan = new ResolvedPlaybackPlan(
flow.Id,
true,
steps,
flow.AsyncCompletionMode);
return true;
}
@@ -20,6 +20,12 @@ namespace AibisDream.FrameAnimation
Always
}
public enum AnimationFlowAsyncCompletionMode
{
CompleteOnTerminalLoopStart = 0,
WaitForTerminalLoopFirstCycle = 1
}
public enum FrameAnimationPlaybackState
{
Stopped,
@@ -95,12 +95,13 @@ namespace AibisDream
public override IEnumerator ChangeStateAsync(string stateName)
{
if (!TryPlay(stateName, false, out var handle))
if (!TryPlayAsync(stateName, out var awaitable))
{
yield break;
}
yield return _player.WaitForFirstPass(handle);
yield return awaitable;
var handle = awaitable.PlaybackHandle;
if (handle.IsCompleted)
{
LogFailedResult(stateName, handle.Result);
@@ -141,6 +142,50 @@ namespace AibisDream
out FrameAnimationPlaybackHandle handle)
{
handle = null;
if (!CanPlay(stateName))
{
return false;
}
handle = restoreTerminalState
? _player.RestoreTerminalState(stateName)
: _player.Play(stateName);
if (handle.IsCompleted &&
handle.Result.Reason == FrameAnimationCompletionReason.Failed)
{
LogFailedResult(stateName, handle.Result);
return false;
}
_currentStateName = stateName;
return true;
}
private bool TryPlayAsync(
string stateName,
out FrameAnimationPlaybackAwaitable awaitable)
{
awaitable = null;
if (!CanPlay(stateName))
{
return false;
}
awaitable = _player.PlayAsync(stateName);
var handle = awaitable.PlaybackHandle;
if (handle.IsCompleted &&
handle.Result.Reason == FrameAnimationCompletionReason.Failed)
{
LogFailedResult(stateName, handle.Result);
return false;
}
_currentStateName = stateName;
return true;
}
private bool CanPlay(string stateName)
{
if (!_isReady || _player == null)
{
Debug.LogError(
@@ -157,17 +202,6 @@ namespace AibisDream
return false;
}
handle = restoreTerminalState
? _player.RestoreTerminalState(stateName)
: _player.Play(stateName);
if (handle.IsCompleted &&
handle.Result.Reason == FrameAnimationCompletionReason.Failed)
{
LogFailedResult(stateName, handle.Result);
return false;
}
_currentStateName = stateName;
return true;
}