{"record":{"id":"a50bf7dc18d247fa","repo":"stride3d/stride","slug":"the-collision-object-has-been-destroyed-simulation","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/Simulation.cs","lineNumber":518,"sourceCode":"                        Distance = point.m_distance1,\n                        Normal = point.m_normalWorldOnB,\n                        PositionOnA = point.m_positionWorldOnA,\n                        PositionOnB = point.m_positionWorldOnB,\n                        AppliedImpulse = point.m_appliedImpulse,\n                        AppliedImpulseLateral1 = point.m_appliedImpulseLateral1,\n                        AppliedImpulseLateral2 = point.m_appliedImpulseLateral2\n                    });\n                }\n            }\n\n            return buffer;\n        }\n\n\n        internal ChannelMicroThreadAwaiter<HashSet<ContactPoint>> ContactChanged(Collision coll)\n        {\n            if (collisions.ContainsKey(coll) == false)\n                throw new InvalidOperationException(\"The collision object has been destroyed.\");\n\n            // Forces this frame's contact to be retrieved and stored so that we can compare it for changes\n            LatestContactPointsFor(coll);\n\n            if (contactChangedChannels.TryGetValue(coll, out var tuple))\n                return tuple.Channel.Receive();\n\n            var channel = channelsPool.Count == 0 ? new Channel<HashSet<ContactPoint>>{ Preference = ChannelPreference.PreferSender } : channelsPool.Pop();\n            contactChangedChannels[coll] = (channel, null);\n            return channel.Receive();\n        }\n\n        /// <summary>\n        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.\n        /// </summary>\n        public void Dispose()\n        {\n            //if (mSoftRigidDynamicsWorld != null) mSoftRigidDynamicsWorld.Dispose();","sourceCodeStart":500,"sourceCodeEnd":536,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/engine/Stride.Physics/Simulation.cs#L500-L536","documentation":"ContactChanged awaits a channel that fires when the contact points of a given Collision change. The Simulation keeps a registry of live collisions (collisions dict); if the passed Collision is not in it, it was never registered or has already been removed (destroyed), so awaiting its changes is meaningless. The library throws InvalidOperationException to signal use of a stale/foreign Collision handle.","triggerScenarios":"Calling simulation.ContactChanged(coll) with a Collision that was already removed from the simulation (its entity/component destroyed and EndContact/cleanup ran), or with a Collision belonging to a different Simulation instance.","commonSituations":"Caching a Collision from a collision-start event and using it later in an async micro-thread after the physics pair separated and was destroyed; running ContactChanged during teardown before cleanup.","solutions":["Check simulation.Collisions (or track collision lifetime) and only call ContactChanged while the collision is still alive.","Restructure async code so the awaiter is cancelled when the collision ends instead of awaiting a destroyed collision.","Verify the Collision came from the same Simulation instance that ContactChanged is called on."],"exampleFix":"// before\nvar awaiter = simulation.ContactChanged(cachedCollision);\n// after\nif (simulation.Collisions.Contains(cachedCollision))\n{\n    var awaiter = simulation.ContactChanged(cachedCollision);\n}\nelse\n{\n    cachedCollision = null; // collision was destroyed; skip\n}","handlingStrategy":"validation","validationCode":"bool canAwait = simulation.Collisions.Contains(collision); // only call ContactChanged when true","typeGuard":"bool IsAlive(Collision c) => c != null && simulation.Collisions.Contains(c);","tryCatchPattern":"try { awaiter = simulation.ContactChanged(coll); } catch (InvalidOperationException) { /* collision destroyed; abandon wait */ }","preventionTips":["Never cache Collision references across frames without checking liveness.","Cancel awaiting micro-threads when the contact pair separates.","Keep Collision usage within the same Simulation instance."],"tags":["physics","game-engine","stride","stale-reference"],"backgroundTag":"entity-not-found","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"}