stride3d/stride · error · InvalidOperationException
All should have hashsets associated to them
Error message
All {nameof(contactChangedChannels)} should have hashsets associated to them What it means
During SendEvents, the Simulation compares the number of previous contact sets against contactChangedChannels.Count and requires a one-to-one mapping between each contact-changed channel and an associated HashSet of contacts. If the counts differ, the internal bookkeeping invariant is broken and InvalidOperationException is thrown.
Solutions
- Ensure every channel added to contactChangedChannels gets its HashSet populated at registration time (atomic add: dictionary[key] = new HashSet<...>())
- Audit code that removes or clears contact sets so the channel entry is removed together with its set
- If this arises in stock Stride code without modifications, report it as a bug — the invariant should hold in unmodified engine code
Example fix
// before contactChangedChannels.Add(channel); // channel without hashset // after contactChangedChannels[channel] = new HashSet<Contact>();
Defensive patterns
Strategy: try-catch
Validate before calling
// ensure every registered channel has an associated hashset before running physics ticks
foreach (var key in contactChangedChannels.Keys)
Debug.Assert(GetHashSetFor(key) != null); Try / catch
try
{
simulation.SendEvents();
}
catch (InvalidOperationException ex) when (ex.Message.Contains("contactChangedChannels"))
{
// rebuild channel -> hashset mapping and re-register contacts
logger.Error(ex, "Contact channel bookkeeping corrupted");
} Prevention
- Always add channels and their hashsets atomically as a single dictionary entry
- Never remove a hashset while keeping its channel key registered
- In stock Stride code, treat this as an engine bug and update the Stride package if hit
When it happens
Trigger: Calling/triggering Simulation.SendEvents when a contact-changed channel was registered in contactChangedChannels without a corresponding HashSet entry (or vice versa), e.g. by adding a channel key without its set or removing a set while keeping the channel.
Common situations: Custom engine modifications or extensions that register contact channels incorrectly; race conditions or partial initialization where channel registration and set creation steps were separated.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Skeleton nodes are not sorted
- An item has been added to a collection that does not have a…
- IsObjectReference returned true for an object that is not…
- Asset of type was migrated, but still its new version…
- Package RootDirectory is null
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/0b7ad3ea0491697d.
Report an issue: GitHub.
Appendix: source
Thrown at sources/engine/Stride.Physics/Simulation.cs:354
foreach (var (coll, hashset) in contactsUpToDate)
{
if (contactChangedChannels.TryGetValue(coll, out var tuple))
{
contactChangedChannels[coll] = (tuple.Channel, hashset);
previousSets++;
}
else
{
hashset.Clear();
contactsPool.Push(hashset);
}
}
contactsUpToDate.Clear();
if (previousSets != contactChangedChannels.Count)
{
throw new InvalidOperationException($"All {nameof(contactChangedChannels)} should have hashsets associated to them");
}
foreach (var collision in markedAsNewColl)
{
if (IncludeStaticAgainstStaticCollisions == false
&& collision.ColliderA is StaticColliderComponent
&& collision.ColliderB is StaticColliderComponent)
{
continue;
}
collision.ColliderA.Collisions.Add( collision );
collision.ColliderB.Collisions.Add( collision );
while (collision.ColliderA.NewPairChannel.Balance < 0)
{
collision.ColliderA.NewPairChannel.Send(collision);
}View on GitHub (pinned to 96fad776d2)