{"record":{"id":"2a1afded77cad120","repo":"stride3d/stride","slug":"the-collision-object-has-been-destroyed","errorCode":null,"errorMessage":"The collision object has been destroyed.","messagePattern":"The collision object has been destroyed\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/engine/Stride.Physics/Collision.cs","lineNumber":54,"sourceCode":"\n        public Collision(PhysicsComponent a, PhysicsComponent b)\n        {\n            ColliderA = a;\n            ColliderB = b;\n        }\n\n        /// <summary>\n        /// The returned collection contains the previous contacts, new contacts are under <see cref=\"Contacts\"/>\n        /// </summary>\n        public ChannelMicroThreadAwaiter<HashSet<ContactPoint>> ContactChanged()\n        {\n            return ColliderA.Simulation.ContactChanged(this);\n        }\n\n        public async Task Ended()\n        {\n            if (ColliderA.Simulation.CurrentCollisions.Contains(this) == false)\n                throw new InvalidOperationException(\"The collision object has been destroyed.\");\n\n            Collision endCollision;\n            do\n            {\n                endCollision = await ColliderA.CollisionEnded();\n            } while (!endCollision.Equals(this));\n        }\n\n        public static bool operator ==(in Collision a, in Collision b)\n        {\n            return (Equals(a.ColliderA, b.ColliderA) && Equals(a.ColliderB, b.ColliderB))\n                   || (Equals(a.ColliderB, b.ColliderA) && Equals(a.ColliderA, b.ColliderB));\n        }\n\n        public static bool operator !=(in Collision a, in Collision b) => (a == b) == false;\n\n        public override bool Equals(object obj)\n        {","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/engine/Stride.Physics/Collision.cs#L36-L72","documentation":"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.","triggerScenarios":"Calling Ended() after the collision pair has already ended (simulation removed it from CurrentCollisions), or on a Collision object retained after physics teardown.","commonSituations":"Caching Collision objects across frames and calling Ended() later; calling Ended() twice; simulation stopped/collider destroyed while an async Ended() was still pending.","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"],"exampleFix":"// before\nawait collision.Ended();\n// after\nif (collision.ColliderA.Simulation.CurrentCollisions.Contains(collision))\n    await collision.Ended();","handlingStrategy":"validation","validationCode":"bool canEnd = collision.ColliderA.Simulation.CurrentCollisions.Contains(collision);\nif (!canEnd) return;","typeGuard":null,"tryCatchPattern":"try { await collision.Ended(); }\ncatch (InvalidOperationException) { /* collision already destroyed; skip */ }","preventionTips":["Use Collision objects immediately, never cache across frames","Always check CurrentCollisions membership before Ended()","Track collision lifetime with the engine's begin/end events"],"tags":["physics","csharp","invalid-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}