Skip to content

Commit a20011e

Browse files
committed
fix: NetworkObject.DeferDespawn not respecting DestroyGameObject parameter
1 parent 870452b commit a20011e

File tree

5 files changed

+72
-10
lines changed

5 files changed

+72
-10
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

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

1313
### Fixed
1414

15+
- Fixed `NetworkObject.DeferDespawn` to respect the `DestroyGameObject` parameter.
1516
- Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212)
1617
- Fixed issue where a spawned `NetworkObject` that was registered with a prefab handler and owned by a client would invoke destroy more than once on the host-server side if the client disconnected while the `NetworkObject` was still spawned. (#3200)
1718

com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ public void Handle(ref NetworkContext context)
150150
{
151151
networkObject.DeferredDespawnTick = DeferredDespawnTick;
152152
var hasCallback = networkObject.OnDeferredDespawnComplete != null;
153-
networkManager.SpawnManager.DeferDespawnNetworkObject(NetworkObjectId, DeferredDespawnTick, hasCallback);
153+
networkManager.SpawnManager.DeferDespawnNetworkObject(NetworkObjectId, DeferredDespawnTick, hasCallback, DestroyGameObject);
154154
return;
155155
}
156156
}

com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,7 +1450,7 @@ internal void ServerSpawnSceneObjectsOnStartSweep()
14501450
}
14511451

14521452
// Since we are spawing in-scene placed NetworkObjects for already loaded scenes,
1453-
// we need to add any in-scene placed NetworkObject to our tracking table
1453+
// we need to add any in-scene placed NetworkObject to our tracking table
14541454
var clearFirst = true;
14551455
foreach (var sceneLoaded in NetworkManager.SceneManager.ScenesLoaded)
14561456
{
@@ -1582,6 +1582,12 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec
15821582
}
15831583
}
15841584

1585+
if (networkObject.IsSceneObject == false && destroyGameObject == false)
1586+
{
1587+
// DANGO-TODO: Check that this is still a valid restriction
1588+
Debug.LogWarning("Only Scene Objects are valid to not be destroyed when despawned");
1589+
}
1590+
15851591
if (m_TargetClientIds.Count > 0 && !NetworkManager.ShutdownInProgress)
15861592
{
15871593
var message = new DestroyObjectMessage
@@ -1920,6 +1926,7 @@ internal struct DeferredDespawnObject
19201926
{
19211927
public int TickToDespawn;
19221928
public bool HasDeferredDespawnCheck;
1929+
public bool DestroyGameObject;
19231930
public ulong NetworkObjectId;
19241931
}
19251932

@@ -1931,12 +1938,13 @@ internal struct DeferredDespawnObject
19311938
/// <param name="networkObjectId">associated NetworkObject</param>
19321939
/// <param name="tickToDespawn">when to despawn the NetworkObject</param>
19331940
/// <param name="hasDeferredDespawnCheck">if true, user script is to be invoked to determine when to despawn</param>
1934-
internal void DeferDespawnNetworkObject(ulong networkObjectId, int tickToDespawn, bool hasDeferredDespawnCheck)
1941+
internal void DeferDespawnNetworkObject(ulong networkObjectId, int tickToDespawn, bool hasDeferredDespawnCheck, bool destroyGameObject)
19351942
{
19361943
var deferredDespawnObject = new DeferredDespawnObject()
19371944
{
19381945
TickToDespawn = tickToDespawn,
19391946
HasDeferredDespawnCheck = hasDeferredDespawnCheck,
1947+
DestroyGameObject = destroyGameObject,
19401948
NetworkObjectId = networkObjectId,
19411949
};
19421950
DeferredDespawnObjects.Add(deferredDespawnObject);
@@ -2001,7 +2009,7 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime)
20012009
}
20022010
var networkObject = SpawnedObjects[deferredObjectEntry.NetworkObjectId];
20032011
// Local instance despawns the instance
2004-
OnDespawnObject(networkObject, true);
2012+
OnDespawnObject(networkObject, deferredObjectEntry.DestroyGameObject);
20052013
DeferredDespawnObjects.Remove(deferredObjectEntry);
20062014
}
20072015
}

com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,25 @@
99

1010
namespace Unity.Netcode.RuntimeTests
1111
{
12+
[TestFixture(SetDestroyGameObject.DestroyGameObject)]
13+
[TestFixture(SetDestroyGameObject.DontDestroyGameObject)]
1214
internal class DeferredDespawningTests : IntegrationTestWithApproximation
1315
{
1416
private const int k_DaisyChainedCount = 5;
1517
protected override int NumberOfClients => 2;
1618
private List<GameObject> m_DaisyChainedDespawnObjects = new List<GameObject>();
1719
private List<ulong> m_HasReachedEnd = new List<ulong>();
1820

19-
public DeferredDespawningTests() : base(HostOrServer.DAHost)
21+
public enum SetDestroyGameObject
2022
{
23+
DestroyGameObject,
24+
DontDestroyGameObject,
25+
}
26+
private bool m_DestroyGameObject;
27+
28+
public DeferredDespawningTests(SetDestroyGameObject destroyGameObject) : base(HostOrServer.DAHost)
29+
{
30+
m_DestroyGameObject = destroyGameObject == SetDestroyGameObject.DestroyGameObject;
2131
}
2232

2333
protected override void OnServerAndClientsCreated()
@@ -45,12 +55,28 @@ protected override void OnServerAndClientsCreated()
4555
[UnityTest]
4656
public IEnumerator DeferredDespawning()
4757
{
58+
// Setup for test
59+
DeferredDespawnDaisyChained.DestroyGameObject = m_DestroyGameObject;
4860
DeferredDespawnDaisyChained.EnableVerbose = m_EnableVerboseDebug;
61+
DeferredDespawnDaisyChained.ClientRelativeInstances = new Dictionary<ulong, Dictionary<ulong, DeferredDespawnDaisyChained>>();
62+
63+
// Spawn the initial object
4964
var rootInstance = SpawnObject(m_DaisyChainedDespawnObjects[0], m_ServerNetworkManager);
5065
DeferredDespawnDaisyChained.ReachedLastChainInstance = ReachedLastChainObject;
66+
67+
// Wait for the chain of objects to spawn and despawn
5168
var timeoutHelper = new TimeoutHelper(300);
5269
yield return WaitForConditionOrTimeOut(HaveAllClientsReachedEndOfChain, timeoutHelper);
5370
AssertOnTimeout($"Timed out waiting for all children to reach the end of their chained deferred despawns!", timeoutHelper);
71+
72+
if (m_DestroyGameObject)
73+
{
74+
Assert.IsTrue(rootInstance == null); // Assert.IsNull doesn't work here
75+
}
76+
else
77+
{
78+
Assert.IsTrue(rootInstance != null); // Assert.IsNotNull doesn't work here
79+
}
5480
}
5581

5682
private bool HaveAllClientsReachedEndOfChain()
@@ -88,9 +114,10 @@ private void ReachedLastChainObject(ulong clientId)
88114
internal class DeferredDespawnDaisyChained : NetworkBehaviour
89115
{
90116
public static bool EnableVerbose;
117+
public static bool DestroyGameObject;
91118
public static Action<ulong> ReachedLastChainInstance;
92119
private const int k_StartingDeferTick = 4;
93-
public static Dictionary<ulong, Dictionary<ulong, DeferredDespawnDaisyChained>> ClientRelativeInstances = new Dictionary<ulong, Dictionary<ulong, DeferredDespawnDaisyChained>>();
120+
public static Dictionary<ulong, Dictionary<ulong, DeferredDespawnDaisyChained>> ClientRelativeInstances;
94121
public bool IsRoot;
95122
public GameObject PrefabToSpawnWhenDespawned;
96123
public bool WasContactedByPeviousChainMember { get; private set; }
@@ -182,7 +209,7 @@ private void InvokeDespawn()
182209
{
183210
FailTest($"[{nameof(InvokeDespawn)}] Client is not the authority but this was invoked (integration test logic issue)!");
184211
}
185-
NetworkObject.DeferDespawn(DeferDespawnTick);
212+
NetworkObject.DeferDespawn(DeferDespawnTick, DestroyGameObject);
186213
}
187214

188215
public override void OnDeferringDespawn(int despawnTick)
@@ -241,7 +268,7 @@ private void Update()
241268
continue;
242269
}
243270

244-
// This should happen shortly afte the instances spawns (based on the deferred despawn count)
271+
// This should happen shortly after the instances spawn (based on the deferred despawn count)
245272
if (!IsRoot && !ClientRelativeInstances[clientId][NetworkObjectId].WasContactedByPeviousChainMember)
246273
{
247274
// exit early if the non-authority instance has not been contacted yet

testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ protected override bool CanStartServerAndClients()
5353
return m_CanStartServerAndClients;
5454
}
5555

56+
public enum DespawnMode
57+
{
58+
Despawn,
59+
DeferredDespawn,
60+
}
61+
5662
/// <summary>
5763
/// This verifies that in-scene placed NetworkObjects will be properly
5864
/// synchronized if:
@@ -61,8 +67,13 @@ protected override bool CanStartServerAndClients()
6167
/// NetworkObject as a NetworkPrefab
6268
/// </summary>
6369
[UnityTest]
64-
public IEnumerator InSceneNetworkObjectSynchAndSpawn()
70+
public IEnumerator InSceneNetworkObjectSynchAndSpawn([Values] DespawnMode despawnMode)
6571
{
72+
if (!m_DistributedAuthority && despawnMode == DespawnMode.DeferredDespawn)
73+
{
74+
Assert.Ignore("Deferred Despawn is only valid with Distributed Authority mode.");
75+
}
76+
6677
NetworkObjectTestComponent.VerboseDebug = true;
6778
// Because despawning a client will cause it to shutdown and clean everything in the
6879
// scene hierarchy, we have to prevent one of the clients from spawning initially before
@@ -99,7 +110,16 @@ public IEnumerator InSceneNetworkObjectSynchAndSpawn()
99110

100111
// Despawn the in-scene placed NetworkObject
101112
Debug.Log("Despawning In-Scene placed NetworkObject");
102-
serverObject.Despawn(false);
113+
114+
if (despawnMode == DespawnMode.Despawn)
115+
{
116+
serverObject.Despawn(false);
117+
}
118+
else
119+
{
120+
serverObject.DeferDespawn(1, false);
121+
}
122+
103123
yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == 0);
104124
AssertOnTimeout($"Timed out waiting for all in-scene instances to be despawned! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()}");
105125

@@ -130,6 +150,12 @@ public IEnumerator InSceneNetworkObjectSynchAndSpawn()
130150
yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == clientCount);
131151
AssertOnTimeout($"Timed out waiting for all in-scene instances to be spawned! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {clientCount}");
132152

153+
if (despawnMode == DespawnMode.DeferredDespawn)
154+
{
155+
// TODO: Check if this is the expected behavior
156+
serverObject.DeferredDespawnTick = 0;
157+
}
158+
133159
// Test NetworkHide on the first client
134160
var firstClientId = m_ClientNetworkManagers[0].LocalClientId;
135161

0 commit comments

Comments
 (0)