08-06-2026, 10:26 AM
Hi,
After upgrading from Obi 7.0.5 to 7.1.1 I started getting the following exception when I enter play mode:
After a bunch of debugging I was able to identify the root cause. Not sure if this is a known issue or not, so I elected to make a post rather than edit the source code with a potential fix.
According to my findings the bug is a race condition during initialization (Application.isPlaying is still false at this point, not sure if relevant)
I believe this is the sequence of events:
1. An ObiCollider's OnEnable fires and creates a collider handle
2. An ObiActor gets enabled, triggering ObiSolver.Initialize() -> ObiColliderWorld.UpdateWorld()
3. During UpdateWorld(), the ObiCollider accesses its Handle property, which creates a rigidbody handle via CreateRigidbody() (after checking if (rigidbodyHandle == null)) and assigns owner - this is HANDLE A (the first handle for this object)
4. ObiRigidbody.OnEnable() fires later and unconditionally calls CreateRigidbody() again, creating HANDLE B for the same body
5. Both handle A and handle B now exist in rigidbodyHandles
6. At some point the ObiRigidbody2D components get disabled, causing OnDisable() to fire for each:
7. This specifically causes an issue for the object that got double-registered, OnDisable() only knows about the handle currently stored in rigidbodyHandle, which is handle B. It calls DestroyRigidbodyData(handleB), which uses a swap-and-remove pattern:
8. Handle B is swapped to the last position, removed, and invalidated. The swap may also move handle A to a new position and update its index. Handle A is never invalidated and no code holds a reference to it anymore, so DestroyRigidbody is never called for it
9. Handle A remains in rigidbodyHandles - still appearing valid (isValid is true), but its owner is a now-destroyed ObiRigidbody2D
10. On the next UpdateWorld() call, iterating rigidbodyHandles hits handle A -> MissingReferenceException
The Handle property getter already has a null-check to prevent duplicate creation, but ObiRigidbody.OnEnable() does not:
// Handle getter - has null check (safe)
// OnEnable - no null check (creates duplicate)
Only one object ends up with a duplicate because the race condition depends on the specific initialization order; only the object whose ObiCollider initializes before its ObiRigidbody AND before the solver finishes initializing will hit this path.
This did not occur in 7.0.5 for me, not really sure why.
The current simple fix that works for me is adding the null check to onEnable() as well; but as I said I wanted to check if this issue has a fix on the way, or if there is a known way to avoid this situation. I also don't get the exception if I enter play mode with the rigidbody disabled then enable it manually later (which makes sense since we avoid the init order issues entirely).
Is there a fix on the way? or a best practice that I'm forgetting that avoids this issue?
Thanks in advance!
After upgrading from Obi 7.0.5 to 7.1.1 I started getting the following exception when I enter play mode:
Code:
MissingReferenceException: The object of type 'Obi.ObiRigidbody2D' has been destroyed but you are still trying to access it.
ObiRigidbody2D.CacheVelocities() at ObiRigidbody2D.cs:61
ObiRigidbody2D.UpdateIfNeeded() at ObiRigidbody2D.cs:71
ObiColliderWorld.UpdateWorld() at ObiColliderWorld.cs:650
ObiSolver.Initialize() at ObiSolver.cs:1330After a bunch of debugging I was able to identify the root cause. Not sure if this is a known issue or not, so I elected to make a post rather than edit the source code with a potential fix.
According to my findings the bug is a race condition during initialization (Application.isPlaying is still false at this point, not sure if relevant)
I believe this is the sequence of events:
1. An ObiCollider's OnEnable fires and creates a collider handle
2. An ObiActor gets enabled, triggering ObiSolver.Initialize() -> ObiColliderWorld.UpdateWorld()
3. During UpdateWorld(), the ObiCollider accesses its Handle property, which creates a rigidbody handle via CreateRigidbody() (after checking if (rigidbodyHandle == null)) and assigns owner - this is HANDLE A (the first handle for this object)
4. ObiRigidbody.OnEnable() fires later and unconditionally calls CreateRigidbody() again, creating HANDLE B for the same body
5. Both handle A and handle B now exist in rigidbodyHandles
6. At some point the ObiRigidbody2D components get disabled, causing OnDisable() to fire for each:
Code:
public void OnDisable()
{
ObiColliderWorld.GetInstance().DestroyRigidbody(rigidbodyHandle);
}7. This specifically causes an issue for the object that got double-registered, OnDisable() only knows about the handle currently stored in rigidbodyHandle, which is handle B. It calls DestroyRigidbodyData(handleB), which uses a swap-and-remove pattern:
Code:
private void DestroyRigidbodyData(ObiRigidbodyHandle handle)
{
if (rigidbodies != null && handle != null && handle.isValid && handle.index < rigidbodyHandles.Count)
{
int index = handle.index;
int lastIndex = rigidbodyHandles.Count - 1;
rigidbodyHandles.Swap(index, lastIndex);
rigidbodies.Swap(index, lastIndex);
rigidbodyHandles[index].index = index; // updates the swapped handle's index
handle.Invalidate();
rigidbodyHandles.RemoveAt(lastIndex);
rigidbodies.count--;
}
}8. Handle B is swapped to the last position, removed, and invalidated. The swap may also move handle A to a new position and update its index. Handle A is never invalidated and no code holds a reference to it anymore, so DestroyRigidbody is never called for it
9. Handle A remains in rigidbodyHandles - still appearing valid (isValid is true), but its owner is a now-destroyed ObiRigidbody2D
10. On the next UpdateWorld() call, iterating rigidbodyHandles hits handle A -> MissingReferenceException
The Handle property getter already has a null-check to prevent duplicate creation, but ObiRigidbody.OnEnable() does not:
// Handle getter - has null check (safe)
Code:
if (rigidbodyHandle == null)
{
rigidbodyHandle = world.CreateRigidbody();
rigidbodyHandle.owner = this;
}// OnEnable - no null check (creates duplicate)
Code:
protected virtual void OnEnable()
{
rigidbodyHandle = ObiColliderWorld.GetInstance().CreateRigidbody();
rigidbodyHandle.owner = this;
}Only one object ends up with a duplicate because the race condition depends on the specific initialization order; only the object whose ObiCollider initializes before its ObiRigidbody AND before the solver finishes initializing will hit this path.
This did not occur in 7.0.5 for me, not really sure why.
The current simple fix that works for me is adding the null check to onEnable() as well; but as I said I wanted to check if this issue has a fix on the way, or if there is a known way to avoid this situation. I also don't get the exception if I enter play mode with the rigidbody disabled then enable it manually later (which makes sense since we avoid the init order issues entirely).
Is there a fix on the way? or a best practice that I'm forgetting that avoids this issue?
Thanks in advance!

