Skip to content

Commit 711aa02

Browse files
feat: Rework the DestroyObject path on the non-authority client (#3291)
* feat: Rework the DestroyObject path on the non-authority client * Changelog.md * update Adding warning if the NetworkObject does not exist since we should not be receiving a message to destroy a NetworkObject if it is either already destroyed =or= the local client is destroying objects when it should not be. * update Changing Debug to NetworkLog --------- Co-authored-by: NoelStephensUnity <[email protected]>
1 parent 069a926 commit 711aa02

File tree

3 files changed

+97
-69
lines changed

3 files changed

+97
-69
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

+1
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 DestroyObject flow on non-authority game clients. (#3291)
1516
- Fixed exception being thrown when a `GameObject` with an associated `NetworkTransform` is disabled. (#3243)
1617
- Fixed issue where the scene migration synchronization table was not cleaned up if the `GameObject` of a `NetworkObject` is destroyed before it should have been. (#3230)
1718
- Fixed issue where the scene migration synchronization table was not cleaned up upon `NetworkManager` shutting down. (#3230)

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

+61-50
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Linq;
2+
using System.Runtime.CompilerServices;
23

34
namespace Unity.Netcode
45
{
@@ -81,7 +82,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int
8182

8283
reader.ReadValueSafe(out DestroyGameObject);
8384

84-
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
85+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
8586
{
8687
// Client-Server mode we always defer where in distributed authority mode we only defer if it is not a targeted destroy
8788
if (!networkManager.DistributedAuthorityMode || (networkManager.DistributedAuthorityMode && !IsTargetedDestroy))
@@ -95,80 +96,90 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int
9596
public void Handle(ref NetworkContext context)
9697
{
9798
var networkManager = (NetworkManager)context.SystemOwner;
99+
networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject);
98100

99-
var networkObject = (NetworkObject)null;
100-
if (!networkManager.DistributedAuthorityMode)
101+
// The DAHost needs to forward despawn messages to the other clients
102+
if (networkManager.DAHost)
101103
{
102-
// If this NetworkObject does not exist on this instance then exit early
103-
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out networkObject))
104-
{
105-
return;
106-
}
107-
}
108-
else
109-
{
110-
networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out networkObject);
111-
if (!networkManager.DAHost && networkObject == null)
104+
HandleDAHostForwardMessage(context.SenderId, ref networkManager, networkObject);
105+
106+
// DAHost adds the object to the queue only if it is not a targeted destroy, or it is and the target is the DAHost client.
107+
if (networkObject && DeferredDespawnTick > 0 && (!IsTargetedDestroy || (IsTargetedDestroy && TargetClientId == 0)))
112108
{
113-
// If this NetworkObject does not exist on this instance then exit early
109+
HandleDeferredDespawn(ref networkManager, ref networkObject);
114110
return;
115111
}
116112
}
117-
// DANGO-TODO: This is just a quick way to foward despawn messages to the remaining clients
118-
if (networkManager.DistributedAuthorityMode && networkManager.DAHost)
113+
114+
// If this NetworkObject does not exist on this instance then exit early
115+
if (!networkObject)
119116
{
120-
var message = new DestroyObjectMessage
121-
{
122-
NetworkObjectId = NetworkObjectId,
123-
DestroyGameObject = DestroyGameObject,
124-
IsDistributedAuthority = true,
125-
IsTargetedDestroy = IsTargetedDestroy,
126-
TargetClientId = TargetClientId, // Just always populate this value whether we write it or not
127-
DeferredDespawnTick = DeferredDespawnTick,
128-
};
129-
var ownerClientId = networkObject == null ? context.SenderId : networkObject.OwnerClientId;
130-
var clientIds = networkObject == null ? networkManager.ConnectedClientsIds.ToList() : networkObject.Observers.ToList();
131-
132-
foreach (var clientId in clientIds)
117+
if (networkManager.LogLevel <= LogLevel.Developer)
133118
{
134-
if (clientId == networkManager.LocalClientId || clientId == ownerClientId)
135-
{
136-
continue;
137-
}
138-
networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
119+
NetworkLog.LogWarning($"[{nameof(DestroyObjectMessage)}] Received destroy object message for NetworkObjectId ({NetworkObjectId}) on Client-{networkManager.LocalClientId}, but that {nameof(NetworkObject)} does not exist!");
139120
}
121+
return;
140122
}
141123

142-
// If we are deferring the despawn, then add it to the deferred despawn queue
143124
if (networkManager.DistributedAuthorityMode)
144125
{
145-
if (DeferredDespawnTick > 0)
126+
// If we are deferring the despawn, then add it to the deferred despawn queue
127+
// If DAHost has reached this point, it is not valid to add to the queue
128+
if (DeferredDespawnTick > 0 && !networkManager.DAHost)
146129
{
147-
// Clients always add it to the queue while DAHost will only add it to the queue if it is not a targeted destroy or it is and the target is the
148-
// DAHost client.
149-
if (!networkManager.DAHost || (networkManager.DAHost && (!IsTargetedDestroy || (IsTargetedDestroy && TargetClientId == 0))))
150-
{
151-
networkObject.DeferredDespawnTick = DeferredDespawnTick;
152-
var hasCallback = networkObject.OnDeferredDespawnComplete != null;
153-
networkManager.SpawnManager.DeferDespawnNetworkObject(NetworkObjectId, DeferredDespawnTick, hasCallback, DestroyGameObject);
154-
return;
155-
}
130+
HandleDeferredDespawn(ref networkManager, ref networkObject);
131+
return;
156132
}
157133

158134
// If this is targeted and we are not the target, then just update our local observers for this object
159-
if (IsTargetedDestroy && TargetClientId != networkManager.LocalClientId && networkObject != null)
135+
if (IsTargetedDestroy && TargetClientId != networkManager.LocalClientId)
160136
{
161137
networkObject.Observers.Remove(TargetClientId);
162138
return;
163139
}
164140
}
165141

166-
if (networkObject != null)
142+
// Otherwise just despawn the NetworkObject right now
143+
networkManager.SpawnManager.OnDespawnNonAuthorityObject(networkObject);
144+
networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize);
145+
}
146+
147+
/// <summary>
148+
/// Handles forwarding the <see cref="DestroyObjectMessage"/> when acting as the DA Host
149+
/// </summary>
150+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
151+
private void HandleDAHostForwardMessage(ulong senderId, ref NetworkManager networkManager, NetworkObject networkObject)
152+
{
153+
var message = new DestroyObjectMessage
167154
{
168-
// Otherwise just despawn the NetworkObject right now
169-
networkManager.SpawnManager.OnDespawnObject(networkObject, DestroyGameObject);
170-
networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize);
155+
NetworkObjectId = NetworkObjectId,
156+
DestroyGameObject = DestroyGameObject,
157+
IsDistributedAuthority = true,
158+
IsTargetedDestroy = IsTargetedDestroy,
159+
TargetClientId = TargetClientId, // Just always populate this value whether we write it or not
160+
DeferredDespawnTick = DeferredDespawnTick,
161+
};
162+
var ownerClientId = networkObject == null ? senderId : networkObject.OwnerClientId;
163+
var clientIds = networkObject == null ? networkManager.ConnectedClientsIds.ToList() : networkObject.Observers.ToList();
164+
165+
foreach (var clientId in clientIds)
166+
{
167+
if (clientId != networkManager.LocalClientId && clientId != ownerClientId)
168+
{
169+
networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
170+
}
171171
}
172172
}
173+
174+
/// <summary>
175+
/// Handles adding to the deferred despawn queue when the <see cref="DestroyObjectMessage"/> indicates a deferred despawn
176+
/// </summary>
177+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
178+
private void HandleDeferredDespawn(ref NetworkManager networkManager, ref NetworkObject networkObject)
179+
{
180+
networkObject.DeferredDespawnTick = DeferredDespawnTick;
181+
var hasCallback = networkObject.OnDeferredDespawnComplete != null;
182+
networkManager.SpawnManager.DeferDespawnNetworkObject(NetworkObjectId, DeferredDespawnTick, hasCallback);
183+
}
173184
}
174185
}

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

+35-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
34
using System.Linq;
45
using System.Text;
56
using UnityEngine;
@@ -1470,7 +1471,6 @@ internal void ServerSpawnSceneObjectsOnStartSweep()
14701471
#else
14711472
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
14721473
#endif
1473-
var isConnectedCMBService = NetworkManager.CMBServiceConnection;
14741474
var networkObjectsToSpawn = new List<NetworkObject>();
14751475
for (int i = 0; i < networkObjects.Length; i++)
14761476
{
@@ -1510,15 +1510,30 @@ internal void ServerSpawnSceneObjectsOnStartSweep()
15101510
networkObjectsToSpawn.Clear();
15111511
}
15121512

1513+
/// <summary>
1514+
/// Called when destroying an object after receiving a <see cref="DestroyObjectMessage"/>.
1515+
/// Processes logic for how to destroy objects on the non-authority client.
1516+
/// </summary>
1517+
internal void OnDespawnNonAuthorityObject([NotNull] NetworkObject networkObject)
1518+
{
1519+
if (networkObject.HasAuthority)
1520+
{
1521+
NetworkLog.LogError($"OnDespawnNonAuthorityObject called on object {networkObject.NetworkObjectId} when is current client {NetworkManager.LocalClientId} has authority on this object.");
1522+
}
1523+
1524+
// On the non-authority, never destroy the game object when InScenePlaced, otherwise always destroy on non-authority side
1525+
OnDespawnObject(networkObject, networkObject.IsSceneObject == false);
1526+
}
1527+
15131528
internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject, bool modeDestroy = false)
15141529
{
1515-
if (NetworkManager == null)
1530+
if (!NetworkManager)
15161531
{
15171532
return;
15181533
}
15191534

15201535
// We have to do this check first as subsequent checks assume we can access NetworkObjectId.
1521-
if (networkObject == null)
1536+
if (!networkObject)
15221537
{
15231538
Debug.LogWarning($"Trying to destroy network object but it is null");
15241539
return;
@@ -1632,8 +1647,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec
16321647
{
16331648
NetworkObjectId = networkObject.NetworkObjectId,
16341649
DeferredDespawnTick = networkObject.DeferredDespawnTick,
1635-
// DANGO-TODO: Reconfigure Client-side object destruction on despawn
1636-
DestroyGameObject = networkObject.IsSceneObject != false ? destroyGameObject : true,
1650+
DestroyGameObject = destroyGameObject,
16371651
IsTargetedDestroy = false,
16381652
IsDistributedAuthority = distributedAuthority,
16391653
};
@@ -2006,7 +2020,6 @@ internal struct DeferredDespawnObject
20062020
{
20072021
public int TickToDespawn;
20082022
public bool HasDeferredDespawnCheck;
2009-
public bool DestroyGameObject;
20102023
public ulong NetworkObjectId;
20112024
}
20122025

@@ -2018,13 +2031,12 @@ internal struct DeferredDespawnObject
20182031
/// <param name="networkObjectId">associated NetworkObject</param>
20192032
/// <param name="tickToDespawn">when to despawn the NetworkObject</param>
20202033
/// <param name="hasDeferredDespawnCheck">if true, user script is to be invoked to determine when to despawn</param>
2021-
internal void DeferDespawnNetworkObject(ulong networkObjectId, int tickToDespawn, bool hasDeferredDespawnCheck, bool destroyGameObject)
2034+
internal void DeferDespawnNetworkObject(ulong networkObjectId, int tickToDespawn, bool hasDeferredDespawnCheck)
20222035
{
20232036
var deferredDespawnObject = new DeferredDespawnObject()
20242037
{
20252038
TickToDespawn = tickToDespawn,
20262039
HasDeferredDespawnCheck = hasDeferredDespawnCheck,
2027-
DestroyGameObject = destroyGameObject,
20282040
NetworkObjectId = networkObjectId,
20292041
};
20302042
DeferredDespawnObjects.Add(deferredDespawnObject);
@@ -2041,15 +2053,21 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime)
20412053
return;
20422054
}
20432055
var currentTick = serverTime.Tick;
2044-
var deferredCallbackCount = DeferredDespawnObjects.Count();
2045-
for (int i = 0; i < deferredCallbackCount; i++)
2056+
var deferredDespawnCount = DeferredDespawnObjects.Count;
2057+
// Loop forward and to process user callbacks and update despawn ticks
2058+
for (int i = 0; i < deferredDespawnCount; i++)
20462059
{
20472060
var deferredObjectEntry = DeferredDespawnObjects[i];
20482061
if (!deferredObjectEntry.HasDeferredDespawnCheck)
20492062
{
20502063
continue;
20512064
}
2052-
var networkObject = SpawnedObjects[deferredObjectEntry.NetworkObjectId];
2065+
2066+
if (!SpawnedObjects.TryGetValue(deferredObjectEntry.NetworkObjectId, out var networkObject))
2067+
{
2068+
continue;
2069+
}
2070+
20532071
// Double check to make sure user did not remove the callback
20542072
if (networkObject.OnDeferredDespawnComplete != null)
20552073
{
@@ -2074,23 +2092,21 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime)
20742092
}
20752093

20762094
// Parse backwards so we can remove objects as we parse through them
2077-
for (int i = DeferredDespawnObjects.Count - 1; i >= 0; i--)
2095+
for (int i = deferredDespawnCount - 1; i >= 0; i--)
20782096
{
20792097
var deferredObjectEntry = DeferredDespawnObjects[i];
20802098
if (deferredObjectEntry.TickToDespawn >= currentTick)
20812099
{
20822100
continue;
20832101
}
20842102

2085-
if (!SpawnedObjects.ContainsKey(deferredObjectEntry.NetworkObjectId))
2103+
if (SpawnedObjects.TryGetValue(deferredObjectEntry.NetworkObjectId, out var networkObject))
20862104
{
2087-
DeferredDespawnObjects.Remove(deferredObjectEntry);
2088-
continue;
2105+
// Local instance despawns the instance
2106+
OnDespawnNonAuthorityObject(networkObject);
20892107
}
2090-
var networkObject = SpawnedObjects[deferredObjectEntry.NetworkObjectId];
2091-
// Local instance despawns the instance
2092-
OnDespawnObject(networkObject, deferredObjectEntry.DestroyGameObject);
2093-
DeferredDespawnObjects.Remove(deferredObjectEntry);
2108+
2109+
DeferredDespawnObjects.RemoveAt(i);
20942110
}
20952111
}
20962112

0 commit comments

Comments
 (0)