-
Notifications
You must be signed in to change notification settings - Fork 450
fix: OnNetworkDespawn not called during shutdown #1390
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
Changes from all commits
09a47b8
d4250af
c3f1e46
76cabca
09a2192
32009fa
616dd96
d7c69b9
6649ece
f978896
e674948
c857ae6
8f0ed76
f6eec53
70045bc
c9c6450
419cd29
c87ad9d
292cd91
03d5c10
7fa748e
d538c1c
51698d9
e2ca11a
36f7d95
1b22d1e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -282,9 +282,13 @@ public NetworkObject NetworkObject | |
m_NetworkObject = GetComponentInParent<NetworkObject>(); | ||
} | ||
|
||
if (m_NetworkObject == null && NetworkLog.CurrentLogLevel <= LogLevel.Normal) | ||
if (m_NetworkObject == null || NetworkManager.Singleton == null || | ||
(NetworkManager.Singleton != null && !NetworkManager.Singleton.ShutdownInProgress)) | ||
Comment on lines
+285
to
+286
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will revisit this. |
||
{ | ||
NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?"); | ||
if (NetworkLog.CurrentLogLevel < LogLevel.Normal) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this area has a high probability to generate a lot of spam, I reduced it to require the NetworkLog level be Developer as there can be several frames (during Update and FixedUpdate) where NetworkBehaviours will make checks that require a reference to the NetworkObject but it doesn't exist. The question here is do we want to generate a warning here since NetworkObject can be null and should be valid to return a null value for m_NetworkObject. |
||
{ | ||
NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?"); | ||
} | ||
} | ||
|
||
return m_NetworkObject; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -225,7 +225,7 @@ private bool TryGetNetworkClient(ulong clientId, out NetworkClient networkClient | |
return NetworkManager.ConnectedClients.TryGetValue(clientId, out networkClient); | ||
} | ||
|
||
if (clientId == NetworkManager.LocalClient.ClientId) | ||
if (NetworkManager.LocalClient != null && clientId == NetworkManager.LocalClient.ClientId) | ||
{ | ||
networkClient = NetworkManager.LocalClient; | ||
return true; | ||
|
@@ -598,25 +598,33 @@ internal void ServerDestroySpawnedSceneObjects() | |
} | ||
} | ||
|
||
internal void DestroyNonSceneObjects() | ||
internal void DespawnAndDestroyNetworkObjects() | ||
{ | ||
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(); | ||
|
||
for (int i = 0; i < networkObjects.Length; i++) | ||
{ | ||
if (networkObjects[i].NetworkManager == NetworkManager) | ||
{ | ||
if (networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value == false) | ||
if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i])) | ||
{ | ||
if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i])) | ||
{ | ||
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]); | ||
OnDespawnObject(networkObjects[i], false); | ||
} | ||
else | ||
{ | ||
UnityEngine.Object.Destroy(networkObjects[i].gameObject); | ||
} | ||
OnDespawnObject(networkObjects[i], false); | ||
// Leave destruction up to the handler | ||
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]); | ||
} | ||
else if (networkObjects[i].IsSpawned) | ||
{ | ||
// If it is an in-scene placed NetworkObject then just despawn | ||
// and let it be destroyed when the scene is unloaded. Otherwise, | ||
// despawn and destroy it. | ||
var shouldDestroy = !(networkObjects[i].IsSceneObject != null | ||
&& networkObjects[i].IsSceneObject.Value); | ||
|
||
OnDespawnObject(networkObjects[i], shouldDestroy); | ||
} | ||
else | ||
{ | ||
UnityEngine.Object.Destroy(networkObjects[i].gameObject); | ||
} | ||
} | ||
} | ||
|
@@ -686,17 +694,21 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec | |
return; | ||
} | ||
|
||
// Move child NetworkObjects to the root when parent NetworkObject is destroyed | ||
foreach (var spawnedNetObj in SpawnedObjectsList) | ||
// If we are shutting down the NetworkManager, then ignore resetting the parent | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @MFatihMAR There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if (NetworkManager == null || !NetworkManager.IsListening)
{
transform.parent = m_CachedParent;
Debug.LogException(new NotListeningException($"{nameof(NetworkManager)} is not listening, start a server or host before reparenting"));
return;
} is this the check you're referring to? if so, we could split that logic up into 2 pieces: also, I think we should not introduce the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is just below:
Basically, if we a shutting down then ignore the parenting code... |
||
if (!NetworkManager.ShutdownInProgress) | ||
{ | ||
var (isReparented, latestParent) = spawnedNetObj.GetNetworkParenting(); | ||
if (isReparented && latestParent == networkObject.NetworkObjectId) | ||
// Move child NetworkObjects to the root when parent NetworkObject is destroyed | ||
foreach (var spawnedNetObj in SpawnedObjectsList) | ||
{ | ||
spawnedNetObj.gameObject.transform.parent = null; | ||
|
||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) | ||
var (isReparented, latestParent) = spawnedNetObj.GetNetworkParenting(); | ||
if (isReparented && latestParent == networkObject.NetworkObjectId) | ||
{ | ||
NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed"); | ||
spawnedNetObj.gameObject.transform.parent = null; | ||
|
||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) | ||
{ | ||
NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed"); | ||
} | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
using System.Collections; | ||
using NUnit.Framework; | ||
using UnityEngine; | ||
using UnityEngine.TestTools; | ||
|
||
|
||
namespace Unity.Netcode.RuntimeTests | ||
{ | ||
/// <summary> | ||
/// Tests that check OnNetworkDespawn being invoked | ||
/// </summary> | ||
public class NetworkObjectOnNetworkDespawnTests | ||
{ | ||
private NetworkManager m_ServerHost; | ||
private NetworkManager[] m_Clients; | ||
|
||
private GameObject m_ObjectToSpawn; | ||
private NetworkObject m_NetworkObject; | ||
|
||
internal class OnNetworkDespawnTestComponent : NetworkBehaviour | ||
{ | ||
public bool OnNetworkDespawnCalled { get; internal set; } | ||
|
||
public override void OnNetworkSpawn() | ||
{ | ||
OnNetworkDespawnCalled = false; | ||
base.OnNetworkSpawn(); | ||
} | ||
|
||
public override void OnNetworkDespawn() | ||
{ | ||
OnNetworkDespawnCalled = true; | ||
base.OnNetworkDespawn(); | ||
} | ||
} | ||
|
||
[UnitySetUp] | ||
public IEnumerator Setup() | ||
{ | ||
Assert.IsTrue(MultiInstanceHelpers.Create(1, out m_ServerHost, out m_Clients)); | ||
|
||
m_ObjectToSpawn = new GameObject(); | ||
m_NetworkObject = m_ObjectToSpawn.AddComponent<NetworkObject>(); | ||
m_ObjectToSpawn.AddComponent<OnNetworkDespawnTestComponent>(); | ||
|
||
// Make it a prefab | ||
MultiInstanceHelpers.MakeNetworkObjectTestPrefab(m_NetworkObject); | ||
|
||
var networkPrefab = new NetworkPrefab(); | ||
networkPrefab.Prefab = m_ObjectToSpawn; | ||
m_ServerHost.NetworkConfig.NetworkPrefabs.Add(networkPrefab); | ||
|
||
foreach (var client in m_Clients) | ||
{ | ||
client.NetworkConfig.NetworkPrefabs.Add(networkPrefab); | ||
} | ||
|
||
yield return null; | ||
} | ||
|
||
[UnityTearDown] | ||
public IEnumerator Teardown() | ||
{ | ||
// Shutdown and clean up both of our NetworkManager instances | ||
if (m_ObjectToSpawn) | ||
{ | ||
Object.Destroy(m_ObjectToSpawn); | ||
m_ObjectToSpawn = null; | ||
} | ||
MultiInstanceHelpers.Destroy(); | ||
yield return null; | ||
} | ||
|
||
public enum InstanceType | ||
{ | ||
Server, | ||
Host, | ||
Client | ||
} | ||
|
||
/// <summary> | ||
/// Tests that a spawned NetworkObject's associated NetworkBehaviours will have | ||
/// their OnNetworkDespawn invoked during NetworkManager shutdown. | ||
/// </summary> | ||
[UnityTest] | ||
public IEnumerator TestNetworkObjectDespawnOnShutdown([Values(InstanceType.Server, InstanceType.Host, InstanceType.Client)] InstanceType despawnCheck) | ||
{ | ||
var useHost = despawnCheck == InstanceType.Server ? false : true; | ||
var networkManager = despawnCheck == InstanceType.Host || despawnCheck == InstanceType.Server ? m_ServerHost : m_Clients[0]; | ||
|
||
// Start the instances | ||
if (!MultiInstanceHelpers.Start(useHost, m_ServerHost, m_Clients)) | ||
{ | ||
Debug.LogError("Failed to start instances"); | ||
Assert.Fail("Failed to start instances"); | ||
} | ||
|
||
// [Client-Side] Wait for a connection to the server | ||
yield return MultiInstanceHelpers.WaitForClientsConnected(m_Clients, null, 512); | ||
|
||
// [Host-Server-Side] Check to make sure all clients are connected | ||
var clientCount = useHost ? m_Clients.Length + 1 : m_Clients.Length; | ||
yield return MultiInstanceHelpers.WaitForClientsConnectedToServer(m_ServerHost, clientCount, null, 512); | ||
|
||
// Spawn the test object | ||
var spawnedObject = Object.Instantiate(m_NetworkObject); | ||
var spawnedNetworkObject = spawnedObject.GetComponent<NetworkObject>(); | ||
spawnedNetworkObject.NetworkManagerOwner = m_ServerHost; | ||
spawnedNetworkObject.Spawn(true); | ||
|
||
// Get the spawned object relative to which NetworkManager instance we are testing. | ||
var relativeSpawnedObject = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>(); | ||
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.GetComponent<OnNetworkDespawnTestComponent>() != null), networkManager, relativeSpawnedObject)); | ||
var onNetworkDespawnTestComponent = relativeSpawnedObject.Result.GetComponent<OnNetworkDespawnTestComponent>(); | ||
|
||
// Confirm it is not set before shutting down the NetworkManager | ||
Assert.IsFalse(onNetworkDespawnTestComponent.OnNetworkDespawnCalled); | ||
|
||
// Shutdown the NetworkManager instance we are testing. | ||
networkManager.Shutdown(); | ||
|
||
// Since shutdown is now delayed until the post frame update | ||
// just wait 2 frames before checking to see if OnNetworkDespawnCalled is true | ||
var currentFrame = Time.frameCount + 2; | ||
yield return new WaitUntil(() => Time.frameCount <= currentFrame); | ||
|
||
// Confirm that OnNetworkDespawn is invoked after shutdown | ||
Assert.IsTrue(onNetworkDespawnTestComponent.OnNetworkDespawnCalled); | ||
} | ||
} | ||
} | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.