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
- Only call Ended() on a Collision you just received from CollisionEnded()/CollisionBeginning while it is still current
- Check ColliderA.Simulation.CurrentCollisions.Contains(collision) before calling Ended()
- 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
- Use Collision objects immediately, never cache across frames
- Always check CurrentCollisions membership before Ended()
- Track collision lifetime with the engine's begin/end events
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
- This instance doesn't contain any path. Use Push() methods…
- Item belongs to another PriorityNodeQueue.
- The control corresponding to the given parentId is a…
- Invoking ShouldStayOpen before the work is finished.
- Cannot resize the manifold store, manifolds have not been…
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)