Skip to content
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

fix: NetworkObject.DeferDespawn not respecting DestroyGameObject parameter #3219

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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,
Expand All @@ -1601,6 +1602,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec
}

networkObject.IsSpawned = false;
networkObject.DeferredDespawnTick = 0;

if (SpawnedObjects.Remove(networkObject.NetworkObjectId))
{
Expand Down Expand Up @@ -1920,6 +1922,7 @@ internal struct DeferredDespawnObject
{
public int TickToDespawn;
public bool HasDeferredDespawnCheck;
public bool DestroyGameObject;
public ulong NetworkObjectId;
}

Expand All @@ -1931,12 +1934,13 @@ internal struct DeferredDespawnObject
/// <param name="networkObjectId">associated NetworkObject</param>
/// <param name="tickToDespawn">when to despawn the NetworkObject</param>
/// <param name="hasDeferredDespawnCheck">if true, user script is to be invoked to determine when to despawn</param>
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);
Expand Down Expand Up @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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();
}

/// <summary>
Expand Down Expand Up @@ -1244,6 +1250,19 @@ public IEnumerator TearDown()
VerboseDebug($"Exiting {nameof(TearDown)}");
LogWaitForMessages();
NetcodeLogAssert.Dispose();

}

/// <summary>
/// Destroys any remaining NetworkManager instances
/// </summary>
private void DestroyNetworkManagers()
{
var networkManagers = Object.FindObjectsByType<NetworkManager>(FindObjectsSortMode.None);
foreach (var networkManager in networkManagers)
{
Object.DestroyImmediate(networkManager.gameObject);
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,25 @@

namespace Unity.Netcode.RuntimeTests
{
[TestFixture(SetDestroyGameObject.DestroyGameObject)]
[TestFixture(SetDestroyGameObject.DontDestroyGameObject)]
internal class DeferredDespawningTests : IntegrationTestWithApproximation
{
private const int k_DaisyChainedCount = 5;
protected override int NumberOfClients => 2;
private List<GameObject> m_DaisyChainedDespawnObjects = new List<GameObject>();
private List<ulong> m_HasReachedEnd = new List<ulong>();

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()
Expand Down Expand Up @@ -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<ulong, Dictionary<ulong, DeferredDespawnDaisyChained>>();

// 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()
Expand Down Expand Up @@ -88,9 +114,10 @@ private void ReachedLastChainObject(ulong clientId)
internal class DeferredDespawnDaisyChained : NetworkBehaviour
{
public static bool EnableVerbose;
public static bool DestroyGameObject;
public static Action<ulong> ReachedLastChainInstance;
private const int k_StartingDeferTick = 4;
public static Dictionary<ulong, Dictionary<ulong, DeferredDespawnDaisyChained>> ClientRelativeInstances = new Dictionary<ulong, Dictionary<ulong, DeferredDespawnDaisyChained>>();
public static Dictionary<ulong, Dictionary<ulong, DeferredDespawnDaisyChained>> ClientRelativeInstances;
public bool IsRoot;
public GameObject PrefabToSpawnWhenDespawned;
public bool WasContactedByPeviousChainMember { get; private set; }
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ protected override bool CanStartServerAndClients()
return m_CanStartServerAndClients;
}

public enum DespawnMode
{
Despawn,
DeferDespawn,
}

/// <summary>
/// This verifies that in-scene placed NetworkObjects will be properly
/// synchronized if:
Expand All @@ -61,8 +67,13 @@ protected override bool CanStartServerAndClients()
/// NetworkObject as a NetworkPrefab
/// </summary>
[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
Expand Down Expand Up @@ -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()}");

Expand Down