stride3d/stride · error · InvalidOperationException

The collision object has been destroyed.

Error message

The collision object has been destroyed.

What it means

Collision.Ended() waits for the collision-pair end event for this Collision. It throws InvalidOperationException if the collision is no longer tracked by the simulation's CurrentCollisions, i.e. the pair already ended or the object was destroyed. Calling Ended() on a stale/destroyed collision is invalid.

Solutions

  1. Only call Ended() on a Collision you just received from CollisionEnded()/CollisionBeginning while it is still current
  2. Check ColliderA.Simulation.CurrentCollisions.Contains(collision) before calling Ended()
  3. Do not cache Collision objects across frames; use them within the same physics step

Example fix

// before
await collision.Ended();
// after
if (collision.ColliderA.Simulation.CurrentCollisions.Contains(collision))
    await collision.Ended();
Defensive patterns

Strategy: validation

Validate before calling

bool canEnd = collision.ColliderA.Simulation.CurrentCollisions.Contains(collision);
if (!canEnd) return;

Try / catch

try { await collision.Ended(); }
catch (InvalidOperationException) { /* collision already destroyed; skip */ }

Prevention

When it happens

Trigger: Calling Ended() after the collision pair has already ended (simulation removed it from CurrentCollisions), or on a Collision object retained after physics teardown.

Common situations: Caching Collision objects across frames and calling Ended() later; calling Ended() twice; simulation stopped/collider destroyed while an async Ended() was still pending.

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/2a1afded77cad120. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.Physics/Collision.cs:54

        public Collision(PhysicsComponent a, PhysicsComponent b)
        {
            ColliderA = a;
            ColliderB = b;
        }

        /// <summary>
        /// The returned collection contains the previous contacts, new contacts are under <see cref="Contacts"/>
        /// </summary>
        public ChannelMicroThreadAwaiter<HashSet<ContactPoint>> ContactChanged()
        {
            return ColliderA.Simulation.ContactChanged(this);
        }

        public async Task Ended()
        {
            if (ColliderA.Simulation.CurrentCollisions.Contains(this) == false)
                throw new InvalidOperationException("The collision object has been destroyed.");

            Collision endCollision;
            do
            {
                endCollision = await ColliderA.CollisionEnded();
            } while (!endCollision.Equals(this));
        }

        public static bool operator ==(in Collision a, in Collision b)
        {
            return (Equals(a.ColliderA, b.ColliderA) && Equals(a.ColliderB, b.ColliderB))
                   || (Equals(a.ColliderB, b.ColliderA) && Equals(a.ColliderA, b.ColliderB));
        }

        public static bool operator !=(in Collision a, in Collision b) => (a == b) == false;

        public override bool Equals(object obj)
        {

View on GitHub (pinned to 96fad776d2)