From f32c34faf4f1bd758190cbebe9cea35188e7838e Mon Sep 17 00:00:00 2001 From: jimdroberts Date: Thu, 1 Oct 2026 19:38:48 -0700 Subject: [PATCH] Update NetworkCollider/2D entered state once per tick, not once per collider The enter/stay/exit bookkeeping ran inside foreach (_colliders) while every iteration shared one per-tick HashSet. With more than one matching collider the set was returned to CollectionCaches while still in use, and twice; hits on later colliders got no callbacks, and hits on the first got OnEnter every tick and never OnExit. Close the loop after the overlap queries and run the bookkeeping once per tick on the union of hits. Applies to NetworkCollider and NetworkCollider2D. --- .../Component/Prediction/NetworkCollider.cs | 116 +++++++++--------- .../Component/Prediction/NetworkCollider2D.cs | 116 +++++++++--------- 2 files changed, 116 insertions(+), 116 deletions(-) diff --git a/Assets/FishNet/Runtime/Generated/Component/Prediction/NetworkCollider.cs b/Assets/FishNet/Runtime/Generated/Component/Prediction/NetworkCollider.cs index 4b3bb210..8356f18c 100644 --- a/Assets/FishNet/Runtime/Generated/Component/Prediction/NetworkCollider.cs +++ b/Assets/FishNet/Runtime/Generated/Component/Prediction/NetworkCollider.cs @@ -174,78 +174,78 @@ protected override void CheckColliders(uint localTick) current.Add(hit); } + } - /* If the colliders already exist then the tick is being - * run again, which would indicate this is being run during a reconcile. - * - * Since this key will have its data replaced with current, store the prior collection.*/ - if (_enteredColliders.TryGetValueIL2CPP(localTick, out HashSet enteredColliders)) - { - CollectionCaches.Store(enteredColliders); - _enteredColliders.Remove(localTick); - } + /* If the colliders already exist then the tick is being + * run again, which would indicate this is being run during a reconcile. + * + * Since this key will have its data replaced with current, store the prior collection.*/ + if (_enteredColliders.TryGetValueIL2CPP(localTick, out HashSet enteredColliders)) + { + CollectionCaches.Store(enteredColliders); + _enteredColliders.Remove(localTick); + } - const uint unsetLastTick = uint.MaxValue; - uint lastTick = localTick > 1 ? localTick - 1 : unsetLastTick; + const uint unsetLastTick = uint.MaxValue; + uint lastTick = localTick > 1 ? localTick - 1 : unsetLastTick; - _enteredColliders.TryGetValueIL2CPP(lastTick, out HashSet lastEnteredColliders); + _enteredColliders.TryGetValueIL2CPP(lastTick, out HashSet lastEnteredColliders); - /* If there are entered colliders then - * update enteredColliders for the tick. */ - if (current.Count > 0) - { - _enteredColliders[localTick] = current; + /* If there are entered colliders then + * update enteredColliders for the tick. */ + if (current.Count > 0) + { + _enteredColliders[localTick] = current; - /* If there were no colliders last tick - * then without a doubt enter should be called since - * the collider could not possibly be present already. */ - if (lastEnteredColliders == null) - { - //Invoke OnEnter for every collider in current. - foreach (Collider c in current) - InvokeOnEnter(c, localTick); - } - /* If the last collection is found then - * check to invoke Enter or Stay. */ - else - { - foreach (Collider c in current) - { - if (lastEnteredColliders.Contains(c)) - OnStay?.Invoke(c, localTick); - else - InvokeOnEnter(c, localTick); - } - } + /* If there were no colliders last tick + * then without a doubt enter should be called since + * the collider could not possibly be present already. */ + if (lastEnteredColliders == null) + { + //Invoke OnEnter for every collider in current. + foreach (Collider c in current) + InvokeOnEnter(c, localTick); } - //If current is empty the collection can be stored. + /* If the last collection is found then + * check to invoke Enter or Stay. */ else { - CollectionCaches.Store(current); + foreach (Collider c in current) + { + if (lastEnteredColliders.Contains(c)) + OnStay?.Invoke(c, localTick); + else + InvokeOnEnter(c, localTick); + } } + } + //If current is empty the collection can be stored. + else + { + CollectionCaches.Store(current); + } - /* Check to invoke OnExit. */ - if (lastEnteredColliders != null) + /* Check to invoke OnExit. */ + if (lastEnteredColliders != null) + { + /* If current does not have the colliders from + * the last tick, then an exit has occurred. */ + foreach (Collider c in lastEnteredColliders) { - /* If current does not have the colliders from - * the last tick, then an exit has occurred. */ - foreach (Collider c in lastEnteredColliders) - { - if (!current.Contains(c)) - OnExit?.Invoke(c, localTick); - } + if (!current.Contains(c)) + OnExit?.Invoke(c, localTick); } + } - /* If the server is started the lastEnteredColliders can - * be discarded since the server will never reconcile, and - * will never need to check them again. */ - if (IsServerStarted) + /* If the server is started the lastEnteredColliders can + * be discarded since the server will never reconcile, and + * will never need to check them again. */ + if (IsServerStarted) + { + if (lastTick is not unsetLastTick && _enteredColliders.TryGetValueIL2CPP(lastTick, out HashSet lEnteredColliders)) { - if (lastTick is not unsetLastTick && _enteredColliders.TryGetValueIL2CPP(lastTick, out HashSet lEnteredColliders)) - { - CollectionCaches.Store(lEnteredColliders); - _enteredColliders.Remove(lastTick); - } + CollectionCaches.Store(lEnteredColliders); + _enteredColliders.Remove(lastTick); } } } diff --git a/Assets/FishNet/Runtime/Generated/Component/Prediction/NetworkCollider2D.cs b/Assets/FishNet/Runtime/Generated/Component/Prediction/NetworkCollider2D.cs index bad08e41..c4069224 100644 --- a/Assets/FishNet/Runtime/Generated/Component/Prediction/NetworkCollider2D.cs +++ b/Assets/FishNet/Runtime/Generated/Component/Prediction/NetworkCollider2D.cs @@ -172,78 +172,78 @@ protected override void CheckColliders(uint localTick) current.Add(hit); } + } - /* If the colliders already exist then the tick is being - * run again, which would indicate this is being run during a reconcile. - * - * Since this key will have its data replaced with current, store the prior collection.*/ - if (_enteredColliders.TryGetValueIL2CPP(localTick, out HashSet enteredColliders)) - { - CollectionCaches.Store(enteredColliders); - _enteredColliders.Remove(localTick); - } + /* If the colliders already exist then the tick is being + * run again, which would indicate this is being run during a reconcile. + * + * Since this key will have its data replaced with current, store the prior collection.*/ + if (_enteredColliders.TryGetValueIL2CPP(localTick, out HashSet enteredColliders)) + { + CollectionCaches.Store(enteredColliders); + _enteredColliders.Remove(localTick); + } - const uint unsetLastTick = uint.MaxValue; - uint lastTick = localTick > 1 ? localTick - 1 : unsetLastTick; + const uint unsetLastTick = uint.MaxValue; + uint lastTick = localTick > 1 ? localTick - 1 : unsetLastTick; - _enteredColliders.TryGetValueIL2CPP(lastTick, out HashSet lastEnteredColliders); + _enteredColliders.TryGetValueIL2CPP(lastTick, out HashSet lastEnteredColliders); - /* If there are entered colliders then - * update enteredColliders for the tick. */ - if (current.Count > 0) - { - _enteredColliders[localTick] = current; + /* If there are entered colliders then + * update enteredColliders for the tick. */ + if (current.Count > 0) + { + _enteredColliders[localTick] = current; - /* If there were no colliders last tick - * then without a doubt enter should be called since - * the collider could not possibly be present already. */ - if (lastEnteredColliders == null) - { - //Invoke OnEnter for every collider in current. - foreach (Collider2D c in current) - InvokeOnEnter(c, localTick); - } - /* If the last collection is found then - * check to invoke Enter or Stay. */ - else - { - foreach (Collider2D c in current) - { - if (lastEnteredColliders.Contains(c)) - OnStay?.Invoke(c, localTick); - else - InvokeOnEnter(c, localTick); - } - } + /* If there were no colliders last tick + * then without a doubt enter should be called since + * the collider could not possibly be present already. */ + if (lastEnteredColliders == null) + { + //Invoke OnEnter for every collider in current. + foreach (Collider2D c in current) + InvokeOnEnter(c, localTick); } - //If current is empty the collection can be stored. + /* If the last collection is found then + * check to invoke Enter or Stay. */ else { - CollectionCaches.Store(current); + foreach (Collider2D c in current) + { + if (lastEnteredColliders.Contains(c)) + OnStay?.Invoke(c, localTick); + else + InvokeOnEnter(c, localTick); + } } + } + //If current is empty the collection can be stored. + else + { + CollectionCaches.Store(current); + } - /* Check to invoke OnExit. */ - if (lastEnteredColliders != null) + /* Check to invoke OnExit. */ + if (lastEnteredColliders != null) + { + /* If current does not have the colliders from + * the last tick, then an exit has occurred. */ + foreach (Collider2D c in lastEnteredColliders) { - /* If current does not have the colliders from - * the last tick, then an exit has occurred. */ - foreach (Collider2D c in lastEnteredColliders) - { - if (!current.Contains(c)) - OnExit?.Invoke(c, localTick); - } + if (!current.Contains(c)) + OnExit?.Invoke(c, localTick); } + } - /* If the server is started the lastEnteredColliders can - * be discarded since the server will never reconcile, and - * will never need to check them again. */ - if (IsServerStarted) + /* If the server is started the lastEnteredColliders can + * be discarded since the server will never reconcile, and + * will never need to check them again. */ + if (IsServerStarted) + { + if (lastTick is not unsetLastTick && _enteredColliders.TryGetValueIL2CPP(lastTick, out HashSet lEnteredColliders)) { - if (lastTick is not unsetLastTick && _enteredColliders.TryGetValueIL2CPP(lastTick, out HashSet lEnteredColliders)) - { - CollectionCaches.Store(lEnteredColliders); - _enteredColliders.Remove(lastTick); - } + CollectionCaches.Store(lEnteredColliders); + _enteredColliders.Remove(lastTick); } } }