Skip to content

fix: prevent exception when showing despawned or destroyed NetworkObject #3029

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
2 changes: 2 additions & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Fixed

- Fixed issue where collections v2.2.x was not supported when using UTP v2.2.x within Unity v2022.3. (#3033)
- Fixed issue where the `NetworkSpawnManager.HandleNetworkObjectShow` could throw an exception if one of the `NetworkObject` components to show was destroyed during the same frame. (#3029)
- Fixed issue where the `NetworkManagerHelper` was continuing to check for hierarchy changes when in play mode. (#3027)

### Changed


## [1.11.0] - 2024-08-20

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1152,7 +1152,24 @@ internal void HandleNetworkObjectShow()
ulong clientId = client.Key;
foreach (var networkObject in client.Value)
{
SendSpawnCallForObject(clientId, networkObject);
// Ignore if null or not spawned (v1.x.x the server should only show what is spawned)
if (networkObject != null && networkObject.IsSpawned)
{
// Prevent exceptions from interrupting this iteration
// so the ObjectsToShowToClient list will be fully processed
// and cleard.
try
{
SendSpawnCallForObject(clientId, networkObject);
}
catch (Exception ex)
{
if (NetworkManager.LogLevel <= LogLevel.Developer)
{
Debug.LogException(ex);
}
}
}
}
}
ObjectsToShowToClient.Clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public enum SceneManagementState
private GameObject m_TestNetworkPrefab;
private bool m_SceneManagementEnabled;

private GameObject m_SpawnedObject;

public NetworkVisibilityTests(SceneManagementState sceneManagementState)
{
m_SceneManagementEnabled = sceneManagementState == SceneManagementState.SceneManagementEnabled;
Expand All @@ -40,7 +42,7 @@ protected override void OnServerAndClientsCreated()

protected override IEnumerator OnServerAndClientsConnected()
{
SpawnObject(m_TestNetworkPrefab, m_ServerNetworkManager);
m_SpawnedObject = SpawnObject(m_TestNetworkPrefab, m_ServerNetworkManager);

yield return base.OnServerAndClientsConnected();
}
Expand All @@ -54,7 +56,43 @@ public IEnumerator HiddenObjectsTest()
yield return WaitForConditionOrTimeOut(() => Object.FindObjectsOfType<NetworkVisibilityComponent>().Where((c) => c.IsSpawned).Count() == 2);
#endif

Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for the visible object count to equal 2!");
AssertOnTimeout("Timed out waiting for the visible object count to equal 2!");
}


[UnityTest]
public IEnumerator HideShowAndDeleteTest()
{
#if UNITY_2023_1_OR_NEWER
yield return WaitForConditionOrTimeOut(() => Object.FindObjectsByType<NetworkVisibilityComponent>(FindObjectsSortMode.None).Where((c) => c.IsSpawned).Count() == 2);
#else
yield return WaitForConditionOrTimeOut(() => Object.FindObjectsOfType<NetworkVisibilityComponent>().Where((c) => c.IsSpawned).Count() == 2);
#endif
AssertOnTimeout("Timed out waiting for the visible object count to equal 2!");

var serverNetworkObject = m_SpawnedObject.GetComponent<NetworkObject>();

serverNetworkObject.NetworkHide(m_ClientNetworkManagers[0].LocalClientId);

#if UNITY_2023_1_OR_NEWER
yield return WaitForConditionOrTimeOut(() => Object.FindObjectsByType<NetworkVisibilityComponent>(FindObjectsSortMode.None).Where((c) => c.IsSpawned).Count() == 1);
#else
yield return WaitForConditionOrTimeOut(() => Object.FindObjectsOfType<NetworkVisibilityComponent>().Where((c) => c.IsSpawned).Count() == 1);
#endif
AssertOnTimeout($"Timed out waiting for {m_SpawnedObject.name} to be hidden from client!");
var networkObjectId = serverNetworkObject.NetworkObjectId;
serverNetworkObject.NetworkShow(m_ClientNetworkManagers[0].LocalClientId);
serverNetworkObject.Despawn(true);

// Expect no exceptions
yield return s_DefaultWaitForTick;

// Now force a scenario where it normally would have caused an exception
m_ServerNetworkManager.SpawnManager.ObjectsToShowToClient.Add(m_ClientNetworkManagers[0].LocalClientId, new System.Collections.Generic.List<NetworkObject>());
m_ServerNetworkManager.SpawnManager.ObjectsToShowToClient[m_ClientNetworkManagers[0].LocalClientId].Add(null);

// Expect no exceptions
yield return s_DefaultWaitForTick;
}
}
}
Loading