stride3d/stride · error · InvalidOperationException

Trying to await end of an animation which is not playing

Error message

Trying to await end of an animation which is not playing

What it means

AnimationComponent.Ended returns a Task that completes when the given PlayingAnimation finishes. It throws InvalidOperationException if the animation instance is not in the current PlayingAnimations collection, because there is nothing to await. This typically means the animation already finished and was removed, or was never started on this component.

Solutions

  1. Check PlayingAnimations.Contains(animation) before calling Ended, or wrap in try/catch for InvalidOperationException.
  2. Call Ended immediately after starting the animation (Crossfade/Blend return the PlayingAnimation).
  3. Track the returned PlayingAnimation and only await once, before it completes.

Example fix

// before
await animComponent.Ended(oldAnim); // may already have been removed
// after
if (animComponent.PlayingAnimations.Contains(oldAnim))
    await animComponent.Ended(oldAnim);
Defensive patterns

Strategy: try-catch

Validate before calling

if (animComponent.PlayingAnimations.Contains(pa))
    await animComponent.Ended(pa);

Try / catch

try { await animComponent.Ended(pa); }
catch (InvalidOperationException) { /* animation already finished/removed; nothing to await */ }

Prevention

When it happens

Trigger: Calling Ended(pa) after the animation completed and was removed from PlayingAnimations, awaiting on a PlayingAnimation obtained from a different AnimationComponent, or a stale reference captured before the list was cleared.

Common situations: Awaiting an animation twice, awaiting after a crossfade faded the animation's weight to zero and it was culled, or race conditions where the animation ends between fetching the reference and calling Ended.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/fdbdb3b4582257eb. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.Engine/Engine/AnimationComponent.cs:201

            return new PlayingAnimation(name, Animations[name]);
        }

        /// <summary>
        /// Returns an awaitable object that will be completed when the animation is removed from the PlayingAnimation list.
        /// </summary>
        /// <remarks>
        /// This happens when:
        /// <list type="bullet">
        /// <item>RepeatMode is PlayOnce and animation reached end</item>
        /// <item>Animation faded out completely (due to blend to 0.0 or crossfade out)</item>
        /// <item>Animation was manually removed from AnimationComponent.PlayingAnimations</item>
        /// </list>
        /// </remarks>
        /// <returns></returns>
        public Task Ended(PlayingAnimation animation)
        {
            if (!PlayingAnimations.Contains(animation))
                throw new InvalidOperationException("Trying to await end of an animation which is not playing");

            if (animation.EndedTCS == null)
            {
                Interlocked.CompareExchange(ref animation.EndedTCS, new TaskCompletionSource<bool>(), null);
            }

            return animation.EndedTCS.Task;
        }
    }

    public interface IBlendTreeBuilder
    {
        void BuildBlendTree(List<AnimationOperation> animationList);
    }
}

View on GitHub (pinned to 96fad776d2)