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

  1. Ensure every channel added to contactChangedChannels gets its HashSet populated at registration time (atomic add: dictionary[key] = new HashSet<...>())
  2. Audit code that removes or clears contact sets so the channel entry is removed together with its set
  3. 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

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


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)