diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md
index 8f2dee46b1..b32356deba 100644
--- a/com.unity.netcode.gameobjects/CHANGELOG.md
+++ b/com.unity.netcode.gameobjects/CHANGELOG.md
@@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Fixed
+- Fixed `NetworkObject.DeferDespawn` to respect the `DestroyGameObject` parameter. (#3219)
- Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212)
- 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)
diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs
index 868d5a2540..cdcff74251 100644
--- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs
@@ -150,7 +150,7 @@ public void Handle(ref NetworkContext context)
{
networkObject.DeferredDespawnTick = DeferredDespawnTick;
var hasCallback = networkObject.OnDeferredDespawnComplete != null;
- networkManager.SpawnManager.DeferDespawnNetworkObject(NetworkObjectId, DeferredDespawnTick, hasCallback);
+ networkManager.SpawnManager.DeferDespawnNetworkObject(NetworkObjectId, DeferredDespawnTick, hasCallback, DestroyGameObject);
return;
}
}
diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
index 854bc8fba2..beba9c7055 100644
--- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
@@ -1450,7 +1450,7 @@ internal void ServerSpawnSceneObjectsOnStartSweep()
}
// Since we are spawing in-scene placed NetworkObjects for already loaded scenes,
- // we need to add any in-scene placed NetworkObject to our tracking table
+ // we need to add any in-scene placed NetworkObject to our tracking table
var clearFirst = true;
foreach (var sceneLoaded in NetworkManager.SceneManager.ScenesLoaded)
{
@@ -1588,6 +1588,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec
{
NetworkObjectId = networkObject.NetworkObjectId,
DeferredDespawnTick = networkObject.DeferredDespawnTick,
+ // DANGO-TODO: Reconfigure Client-side object destruction on despawn
DestroyGameObject = networkObject.IsSceneObject != false ? destroyGameObject : true,
IsTargetedDestroy = false,
IsDistributedAuthority = distributedAuthority,
@@ -1601,6 +1602,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec
}
networkObject.IsSpawned = false;
+ networkObject.DeferredDespawnTick = 0;
if (SpawnedObjects.Remove(networkObject.NetworkObjectId))
{
@@ -1920,6 +1922,7 @@ internal struct DeferredDespawnObject
{
public int TickToDespawn;
public bool HasDeferredDespawnCheck;
+ public bool DestroyGameObject;
public ulong NetworkObjectId;
}
@@ -1931,12 +1934,13 @@ internal struct DeferredDespawnObject
/// associated NetworkObject
/// when to despawn the NetworkObject
/// if true, user script is to be invoked to determine when to despawn
- internal void DeferDespawnNetworkObject(ulong networkObjectId, int tickToDespawn, bool hasDeferredDespawnCheck)
+ internal void DeferDespawnNetworkObject(ulong networkObjectId, int tickToDespawn, bool hasDeferredDespawnCheck, bool destroyGameObject)
{
var deferredDespawnObject = new DeferredDespawnObject()
{
TickToDespawn = tickToDespawn,
HasDeferredDespawnCheck = hasDeferredDespawnCheck,
+ DestroyGameObject = destroyGameObject,
NetworkObjectId = networkObjectId,
};
DeferredDespawnObjects.Add(deferredDespawnObject);
@@ -2001,7 +2005,7 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime)
}
var networkObject = SpawnedObjects[deferredObjectEntry.NetworkObjectId];
// Local instance despawns the instance
- OnDespawnObject(networkObject, true);
+ OnDespawnObject(networkObject, deferredObjectEntry.DestroyGameObject);
DeferredDespawnObjects.Remove(deferredObjectEntry);
}
}
diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs
index 921d70b928..a6e31a1a49 100644
--- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs
+++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs
@@ -1156,6 +1156,9 @@ protected void ShutdownAndCleanUp()
// reset the m_ServerWaitForTick for the next test to initialize
s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / k_DefaultTickRate);
VerboseDebug($"Exiting {nameof(ShutdownAndCleanUp)}");
+
+ // Assure any remaining NetworkManagers are destroyed
+ DestroyNetworkManagers();
}
protected IEnumerator CoroutineShutdownAndCleanUp()
@@ -1195,6 +1198,9 @@ protected IEnumerator CoroutineShutdownAndCleanUp()
// reset the m_ServerWaitForTick for the next test to initialize
s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / k_DefaultTickRate);
VerboseDebug($"Exiting {nameof(ShutdownAndCleanUp)}");
+
+ // Assure any remaining NetworkManagers are destroyed
+ DestroyNetworkManagers();
}
///
@@ -1244,6 +1250,19 @@ public IEnumerator TearDown()
VerboseDebug($"Exiting {nameof(TearDown)}");
LogWaitForMessages();
NetcodeLogAssert.Dispose();
+
+ }
+
+ ///
+ /// Destroys any remaining NetworkManager instances
+ ///
+ private void DestroyNetworkManagers()
+ {
+ var networkManagers = Object.FindObjectsByType(FindObjectsSortMode.None);
+ foreach (var networkManager in networkManagers)
+ {
+ Object.DestroyImmediate(networkManager.gameObject);
+ }
}
///
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs
index faba405079..5787f169f1 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs
@@ -9,6 +9,8 @@
namespace Unity.Netcode.RuntimeTests
{
+ [TestFixture(SetDestroyGameObject.DestroyGameObject)]
+ [TestFixture(SetDestroyGameObject.DontDestroyGameObject)]
internal class DeferredDespawningTests : IntegrationTestWithApproximation
{
private const int k_DaisyChainedCount = 5;
@@ -16,8 +18,16 @@ internal class DeferredDespawningTests : IntegrationTestWithApproximation
private List m_DaisyChainedDespawnObjects = new List();
private List m_HasReachedEnd = new List();
- public DeferredDespawningTests() : base(HostOrServer.DAHost)
+ public enum SetDestroyGameObject
{
+ DestroyGameObject,
+ DontDestroyGameObject,
+ }
+ private bool m_DestroyGameObject;
+
+ public DeferredDespawningTests(SetDestroyGameObject destroyGameObject) : base(HostOrServer.DAHost)
+ {
+ m_DestroyGameObject = destroyGameObject == SetDestroyGameObject.DestroyGameObject;
}
protected override void OnServerAndClientsCreated()
@@ -45,12 +55,28 @@ protected override void OnServerAndClientsCreated()
[UnityTest]
public IEnumerator DeferredDespawning()
{
+ // Setup for test
+ DeferredDespawnDaisyChained.DestroyGameObject = m_DestroyGameObject;
DeferredDespawnDaisyChained.EnableVerbose = m_EnableVerboseDebug;
+ DeferredDespawnDaisyChained.ClientRelativeInstances = new Dictionary>();
+
+ // Spawn the initial object
var rootInstance = SpawnObject(m_DaisyChainedDespawnObjects[0], m_ServerNetworkManager);
DeferredDespawnDaisyChained.ReachedLastChainInstance = ReachedLastChainObject;
+
+ // Wait for the chain of objects to spawn and despawn
var timeoutHelper = new TimeoutHelper(300);
yield return WaitForConditionOrTimeOut(HaveAllClientsReachedEndOfChain, timeoutHelper);
AssertOnTimeout($"Timed out waiting for all children to reach the end of their chained deferred despawns!", timeoutHelper);
+
+ if (m_DestroyGameObject)
+ {
+ Assert.IsTrue(rootInstance == null); // Assert.IsNull doesn't work here
+ }
+ else
+ {
+ Assert.IsTrue(rootInstance != null); // Assert.IsNotNull doesn't work here
+ }
}
private bool HaveAllClientsReachedEndOfChain()
@@ -88,9 +114,10 @@ private void ReachedLastChainObject(ulong clientId)
internal class DeferredDespawnDaisyChained : NetworkBehaviour
{
public static bool EnableVerbose;
+ public static bool DestroyGameObject;
public static Action ReachedLastChainInstance;
private const int k_StartingDeferTick = 4;
- public static Dictionary> ClientRelativeInstances = new Dictionary>();
+ public static Dictionary> ClientRelativeInstances;
public bool IsRoot;
public GameObject PrefabToSpawnWhenDespawned;
public bool WasContactedByPeviousChainMember { get; private set; }
@@ -182,7 +209,7 @@ private void InvokeDespawn()
{
FailTest($"[{nameof(InvokeDespawn)}] Client is not the authority but this was invoked (integration test logic issue)!");
}
- NetworkObject.DeferDespawn(DeferDespawnTick);
+ NetworkObject.DeferDespawn(DeferDespawnTick, DestroyGameObject);
}
public override void OnDeferringDespawn(int despawnTick)
@@ -241,7 +268,7 @@ private void Update()
continue;
}
- // This should happen shortly afte the instances spawns (based on the deferred despawn count)
+ // This should happen shortly after the instances spawn (based on the deferred despawn count)
if (!IsRoot && !ClientRelativeInstances[clientId][NetworkObjectId].WasContactedByPeviousChainMember)
{
// exit early if the non-authority instance has not been contacted yet
diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs
index 59922c5959..f0def620ff 100644
--- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs
+++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs
@@ -53,6 +53,12 @@ protected override bool CanStartServerAndClients()
return m_CanStartServerAndClients;
}
+ public enum DespawnMode
+ {
+ Despawn,
+ DeferDespawn,
+ }
+
///
/// This verifies that in-scene placed NetworkObjects will be properly
/// synchronized if:
@@ -61,8 +67,13 @@ protected override bool CanStartServerAndClients()
/// NetworkObject as a NetworkPrefab
///
[UnityTest]
- public IEnumerator InSceneNetworkObjectSynchAndSpawn()
+ public IEnumerator InSceneNetworkObjectSynchAndSpawn([Values] DespawnMode despawnMode)
{
+ if (!m_DistributedAuthority && despawnMode == DespawnMode.DeferDespawn)
+ {
+ Assert.Ignore($"Test ignored as DeferDespawn is only valid with Distributed Authority mode.");
+ }
+
NetworkObjectTestComponent.VerboseDebug = true;
// Because despawning a client will cause it to shutdown and clean everything in the
// scene hierarchy, we have to prevent one of the clients from spawning initially before
@@ -99,7 +110,16 @@ public IEnumerator InSceneNetworkObjectSynchAndSpawn()
// Despawn the in-scene placed NetworkObject
Debug.Log("Despawning In-Scene placed NetworkObject");
- serverObject.Despawn(false);
+
+ if (despawnMode == DespawnMode.Despawn)
+ {
+ serverObject.Despawn(false);
+ }
+ else
+ {
+ serverObject.DeferDespawn(1, false);
+ }
+
yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == 0);
AssertOnTimeout($"Timed out waiting for all in-scene instances to be despawned! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()}");