Skip to content

Commit bc23a35

Browse files
NoelStephensUnityJesse Olmer
andauthored
fix: Nested NetworkBehaviours don't de-register or Invoke OnNetworkDespawn if destroyed while the parent NetworkObject remains spawned [NCCBUG-137] (#2091)
* fix NCCBUG-137 This resolves the issue where destroying a GameObject with one or more NetworkBehaviour components that is a child or any child generation of the NetworkObject/GameObject that theNetworkBehaviour(s) are assigned to would still try to access the destroyed NetworkBehaviour since it was still an entry within the NetworkObject.ChildNetworkBehaviours list. It also resolves the issue where under this scenario the NetworkBehaviours would not have their OnNetworkDespawn method invoked. * update NCCBUG-137 updating the changelog * fix We need to check IsSpawned when logging a warning about a NetworkBehaviour not having an assigned NetworkObject (this is only valid if NetworkBehaviour.IsSpawned == true) * fix Needed to adjust to account for the condition when it is valid is when you have a NetworkBehaviour that is spawned but there is no assigned NetworkObject. * test NCCBUG-137 This is the integration validation test for this PR fix. * update Adding PR number to the changelog entry * test adding check for OnNetworkDespawn being invoked when a child NetworkBehaviour is destroyed. but the parent NetworkObject persists and remains spawned. * style MTT-4260 was created for this technical debt. removing reminder to create a ticket. * fix fixing merge issue. * fix Removing the invocation of NetworkBehaviour's OnNetworkDespawn when a NetworkBehaviour is destroyed while a NetworkObject is still spawned. * style removing unused namespace. * Update com.unity.netcode.gameobjects/CHANGELOG.md Co-authored-by: Jesse Olmer <[email protected]> Co-authored-by: Jesse Olmer <[email protected]>
1 parent afce30f commit bc23a35

File tree

4 files changed

+77
-1
lines changed

4 files changed

+77
-1
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1616

1717
- Fixed issue where a client owner of a `NetworkVariable` with both owner read and write permissions would not update the server side when changed. (#2097)
1818
- Fixed issue when attempting to spawn a parent `GameObject`, with `NetworkObject` component attached, that has one or more child `GameObject`s, that are inactive in the hierarchy, with `NetworkBehaviour` components it will no longer attempt to spawn the associated `NetworkBehaviour`(s) or invoke ownership changed notifications but will log a warning message. (#2096)
19+
- Fixed an issue where destroying a NetworkBehaviour would not deregister it from the parent NetworkObject, leading to exceptions when the parent was later destroyed. (#2091)
1920
- Fixed issue where `NetworkObject.NetworkHide` was despawning and destroying, as opposed to only despawning, in-scene placed `NetworkObject`s. (#2086)
2021
- Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076)
2122
- Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074)

com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,8 @@ public NetworkObject NetworkObject
331331
// in Update and/or in FixedUpdate could still be checking NetworkBehaviour.NetworkObject directly (i.e. does it exist?)
332332
// or NetworkBehaviour.IsSpawned (i.e. to early exit if not spawned) which, in turn, could generate several Warning messages
333333
// per spawned NetworkObject. Checking for ShutdownInProgress prevents these unnecessary LogWarning messages.
334-
if (m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress))
334+
// We must check IsSpawned, otherwise a warning will be logged under certain valid conditions (see OnDestroy)
335+
if (IsSpawned && m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress))
335336
{
336337
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
337338
{
@@ -759,6 +760,14 @@ protected NetworkObject GetNetworkObject(ulong networkId)
759760
/// </summary>
760761
public virtual void OnDestroy()
761762
{
763+
if (NetworkObject != null && NetworkObject.IsSpawned && IsSpawned)
764+
{
765+
// If the associated NetworkObject is still spawned then this
766+
// NetworkBehaviour will be removed from the NetworkObject's
767+
// ChildNetworkBehaviours list.
768+
NetworkObject.OnNetworkBehaviourDestroyed(this);
769+
}
770+
762771
// this seems odd to do here, but in fact especially in tests we can find ourselves
763772
// here without having called InitializedVariables, which causes problems if any
764773
// of those variables use native containers (e.g. NetworkList) as they won't be
@@ -770,6 +779,7 @@ public virtual void OnDestroy()
770779
InitializeVariables();
771780
}
772781

782+
773783
for (int i = 0; i < NetworkVariableFields.Count; i++)
774784
{
775785
NetworkVariableFields[i].Dispose();

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,5 +1219,21 @@ internal uint HostCheckForGlobalObjectIdHashOverride()
12191219

12201220
return GlobalObjectIdHash;
12211221
}
1222+
1223+
/// <summary>
1224+
/// Removes a NetworkBehaviour from the ChildNetworkBehaviours list when destroyed
1225+
/// while the NetworkObject is still spawned.
1226+
/// </summary>
1227+
internal void OnNetworkBehaviourDestroyed(NetworkBehaviour networkBehaviour)
1228+
{
1229+
if (networkBehaviour.IsSpawned && IsSpawned)
1230+
{
1231+
if (NetworkManager.LogLevel == LogLevel.Developer)
1232+
{
1233+
NetworkLog.LogWarning($"{nameof(NetworkBehaviour)}-{networkBehaviour.name} is being destroyed while {nameof(NetworkObject)}-{name} is still spawned! (could break state synchronization)");
1234+
}
1235+
ChildNetworkBehaviours.Remove(networkBehaviour);
1236+
}
1237+
}
12221238
}
12231239
}

com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ protected override bool CanStartServerAndClients()
2222

2323
public class SimpleNetworkBehaviour : NetworkBehaviour
2424
{
25+
public bool OnNetworkDespawnCalled;
26+
27+
public override void OnNetworkDespawn()
28+
{
29+
OnNetworkDespawnCalled = true;
30+
base.OnNetworkDespawn();
31+
}
2532
}
2633

2734
protected override IEnumerator OnSetup()
@@ -83,6 +90,9 @@ public IEnumerator ValidateNoSpam()
8390
// set the log level to developer
8491
m_ServerNetworkManager.LogLevel = LogLevel.Developer;
8592

93+
// The only valid condition for this would be if the NetworkBehaviour is spawned.
94+
simpleNetworkBehaviour.IsSpawned = true;
95+
8696
// Verify the warning gets logged under normal conditions
8797
var isNull = simpleNetworkBehaviour.NetworkObject == null;
8898
LogAssert.Expect(LogType.Warning, $"[Netcode] Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
@@ -98,5 +108,44 @@ public IEnumerator ValidateNoSpam()
98108
networkObjectToTest.Despawn();
99109
Object.Destroy(networkObjectToTest);
100110
}
111+
112+
/// <summary>
113+
/// This validates the fix for when a child GameObject with a NetworkBehaviour
114+
/// is deleted while the parent GameObject with a NetworkObject is spawned and
115+
/// is not deleted until a later time would cause an exception due to the
116+
/// NetworkBehaviour not being removed from the NetworkObject.ChildNetworkBehaviours
117+
/// list.
118+
/// </summary>
119+
[UnityTest]
120+
public IEnumerator ValidateDeleteChildNetworkBehaviour()
121+
{
122+
m_AllowServerToStart = true;
123+
124+
yield return s_DefaultWaitForTick;
125+
126+
// Now just start the Host
127+
yield return StartServerAndClients();
128+
129+
var parentObject = new GameObject();
130+
var childObject = new GameObject();
131+
childObject.transform.parent = parentObject.transform;
132+
var parentNetworkObject = parentObject.AddComponent<NetworkObject>();
133+
childObject.AddComponent<SimpleNetworkBehaviour>();
134+
135+
parentNetworkObject.Spawn();
136+
yield return s_DefaultWaitForTick;
137+
138+
// Destroy the child object with child NetworkBehaviour
139+
Object.Destroy(childObject);
140+
141+
yield return s_DefaultWaitForTick;
142+
143+
// Assure no log messages are logged when they should not be logged
144+
LogAssert.NoUnexpectedReceived();
145+
146+
// Destroy the parent object which should not cause any exceptions
147+
// (validating the fix)
148+
Object.Destroy(parentObject);
149+
}
101150
}
102151
}

0 commit comments

Comments
 (0)