stride3d/stride · error · InvalidOperationException
The collision object has been destroyed.
Error message
The collision object has been destroyed.
What it means
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.
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.
Example fix
// before
var awaiter = simulation.ContactChanged(cachedCollision);
// after
if (simulation.Collisions.Contains(cachedCollision))
{
var awaiter = simulation.ContactChanged(cachedCollision);
}
else
{
cachedCollision = null; // collision was destroyed; skip
} Defensive patterns
Strategy: validation
Validate before calling
bool canAwait = simulation.Collisions.Contains(collision); // only call ContactChanged when true
Type guard
bool IsAlive(Collision c) => c != null && simulation.Collisions.Contains(c);
Try / catch
try { awaiter = simulation.ContactChanged(coll); } catch (InvalidOperationException) { /* collision destroyed; abandon wait */ } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- The corresponding UI Element could not be found in the…
- The corresponding UI element could not be found in the…
- Url must be valid, assign null instead
- Custom strides is not supported with packed PixelFormats
- Please add a CharacterComponent to the entity containing…
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/a50bf7dc18d247fa.
Report an issue: GitHub.
Appendix: source
Thrown at sources/engine/Stride.Physics/Simulation.cs:518
Distance = point.m_distance1,
Normal = point.m_normalWorldOnB,
PositionOnA = point.m_positionWorldOnA,
PositionOnB = point.m_positionWorldOnB,
AppliedImpulse = point.m_appliedImpulse,
AppliedImpulseLateral1 = point.m_appliedImpulseLateral1,
AppliedImpulseLateral2 = point.m_appliedImpulseLateral2
});
}
}
return buffer;
}
internal ChannelMicroThreadAwaiter<HashSet<ContactPoint>> ContactChanged(Collision coll)
{
if (collisions.ContainsKey(coll) == false)
throw new InvalidOperationException("The collision object has been destroyed.");
// Forces this frame's contact to be retrieved and stored so that we can compare it for changes
LatestContactPointsFor(coll);
if (contactChangedChannels.TryGetValue(coll, out var tuple))
return tuple.Channel.Receive();
var channel = channelsPool.Count == 0 ? new Channel<HashSet<ContactPoint>>{ Preference = ChannelPreference.PreferSender } : channelsPool.Pop();
contactChangedChannels[coll] = (channel, null);
return channel.Receive();
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
//if (mSoftRigidDynamicsWorld != null) mSoftRigidDynamicsWorld.Dispose();View on GitHub (pinned to 96fad776d2)