Skip to content

Commit c3ef7b3

Browse files
committed
feat: Rework the DestroyObject path on the non-authority client
1 parent 91ebe96 commit c3ef7b3

File tree

2 files changed

+95
-71
lines changed

2 files changed

+95
-71
lines changed

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

Lines changed: 60 additions & 52 deletions
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,87 @@ 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)
133-
{
134-
if (clientId == networkManager.LocalClientId || clientId == ownerClientId)
135-
{
136-
continue;
137-
}
138-
networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
139-
}
117+
return;
140118
}
141119

142-
// If we are deferring the despawn, then add it to the deferred despawn queue
143120
if (networkManager.DistributedAuthorityMode)
144121
{
145-
if (DeferredDespawnTick > 0)
122+
// If we are deferring the despawn, then add it to the deferred despawn queue
123+
// If DAHost has reached this point, it is not valid to add to the queue
124+
if (DeferredDespawnTick > 0 && !networkManager.DAHost)
146125
{
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-
}
126+
HandleDeferredDespawn(ref networkManager, ref networkObject);
127+
return;
156128
}
157129

158130
// 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)
131+
if (IsTargetedDestroy && TargetClientId != networkManager.LocalClientId)
160132
{
161133
networkObject.Observers.Remove(TargetClientId);
162134
return;
163135
}
164136
}
165137

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

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

Lines changed: 35 additions & 19 deletions
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;
@@ -1461,7 +1462,6 @@ internal void ServerSpawnSceneObjectsOnStartSweep()
14611462
#else
14621463
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
14631464
#endif
1464-
var isConnectedCMBService = NetworkManager.CMBServiceConnection;
14651465
var networkObjectsToSpawn = new List<NetworkObject>();
14661466
for (int i = 0; i < networkObjects.Length; i++)
14671467
{
@@ -1501,15 +1501,30 @@ internal void ServerSpawnSceneObjectsOnStartSweep()
15011501
networkObjectsToSpawn.Clear();
15021502
}
15031503

1504+
/// <summary>
1505+
/// Called when destroying an object after receiving a <see cref="DestroyObjectMessage"/>.
1506+
/// Processes logic for how to destroy objects on the non-authority client.
1507+
/// </summary>
1508+
internal void OnDespawnNonAuthorityObject([NotNull] NetworkObject networkObject)
1509+
{
1510+
if (networkObject.HasAuthority)
1511+
{
1512+
Debug.LogError($"OnDespawnNonAuthorityObject called on object {networkObject.NetworkObjectId} when is current client {NetworkManager.LocalClientId} has authority on this object.");
1513+
}
1514+
1515+
// On the non-authority, never destroy the game object when InScenePlaced, otherwise always destroy on non-authority side
1516+
OnDespawnObject(networkObject, networkObject.IsSceneObject == false);
1517+
}
1518+
15041519
internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject, bool modeDestroy = false)
15051520
{
1506-
if (NetworkManager == null)
1521+
if (!NetworkManager)
15071522
{
15081523
return;
15091524
}
15101525

15111526
// We have to do this check first as subsequent checks assume we can access NetworkObjectId.
1512-
if (networkObject == null)
1527+
if (!networkObject)
15131528
{
15141529
Debug.LogWarning($"Trying to destroy network object but it is null");
15151530
return;
@@ -1623,8 +1638,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec
16231638
{
16241639
NetworkObjectId = networkObject.NetworkObjectId,
16251640
DeferredDespawnTick = networkObject.DeferredDespawnTick,
1626-
// DANGO-TODO: Reconfigure Client-side object destruction on despawn
1627-
DestroyGameObject = networkObject.IsSceneObject != false ? destroyGameObject : true,
1641+
DestroyGameObject = destroyGameObject,
16281642
IsTargetedDestroy = false,
16291643
IsDistributedAuthority = distributedAuthority,
16301644
};
@@ -1997,7 +2011,6 @@ internal struct DeferredDespawnObject
19972011
{
19982012
public int TickToDespawn;
19992013
public bool HasDeferredDespawnCheck;
2000-
public bool DestroyGameObject;
20012014
public ulong NetworkObjectId;
20022015
}
20032016

@@ -2009,13 +2022,12 @@ internal struct DeferredDespawnObject
20092022
/// <param name="networkObjectId">associated NetworkObject</param>
20102023
/// <param name="tickToDespawn">when to despawn the NetworkObject</param>
20112024
/// <param name="hasDeferredDespawnCheck">if true, user script is to be invoked to determine when to despawn</param>
2012-
internal void DeferDespawnNetworkObject(ulong networkObjectId, int tickToDespawn, bool hasDeferredDespawnCheck, bool destroyGameObject)
2025+
internal void DeferDespawnNetworkObject(ulong networkObjectId, int tickToDespawn, bool hasDeferredDespawnCheck)
20132026
{
20142027
var deferredDespawnObject = new DeferredDespawnObject()
20152028
{
20162029
TickToDespawn = tickToDespawn,
20172030
HasDeferredDespawnCheck = hasDeferredDespawnCheck,
2018-
DestroyGameObject = destroyGameObject,
20192031
NetworkObjectId = networkObjectId,
20202032
};
20212033
DeferredDespawnObjects.Add(deferredDespawnObject);
@@ -2032,15 +2044,21 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime)
20322044
return;
20332045
}
20342046
var currentTick = serverTime.Tick;
2035-
var deferredCallbackCount = DeferredDespawnObjects.Count();
2036-
for (int i = 0; i < deferredCallbackCount; i++)
2047+
var deferredDespawnCount = DeferredDespawnObjects.Count;
2048+
// Loop forward and to process user callbacks and update despawn ticks
2049+
for (int i = 0; i < deferredDespawnCount; i++)
20372050
{
20382051
var deferredObjectEntry = DeferredDespawnObjects[i];
20392052
if (!deferredObjectEntry.HasDeferredDespawnCheck)
20402053
{
20412054
continue;
20422055
}
2043-
var networkObject = SpawnedObjects[deferredObjectEntry.NetworkObjectId];
2056+
2057+
if (!SpawnedObjects.TryGetValue(deferredObjectEntry.NetworkObjectId, out var networkObject))
2058+
{
2059+
continue;
2060+
}
2061+
20442062
// Double check to make sure user did not remove the callback
20452063
if (networkObject.OnDeferredDespawnComplete != null)
20462064
{
@@ -2065,23 +2083,21 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime)
20652083
}
20662084

20672085
// Parse backwards so we can remove objects as we parse through them
2068-
for (int i = DeferredDespawnObjects.Count - 1; i >= 0; i--)
2086+
for (int i = deferredDespawnCount - 1; i >= 0; i--)
20692087
{
20702088
var deferredObjectEntry = DeferredDespawnObjects[i];
20712089
if (deferredObjectEntry.TickToDespawn >= currentTick)
20722090
{
20732091
continue;
20742092
}
20752093

2076-
if (!SpawnedObjects.ContainsKey(deferredObjectEntry.NetworkObjectId))
2094+
if (SpawnedObjects.TryGetValue(deferredObjectEntry.NetworkObjectId, out var networkObject))
20772095
{
2078-
DeferredDespawnObjects.Remove(deferredObjectEntry);
2079-
continue;
2096+
// Local instance despawns the instance
2097+
OnDespawnNonAuthorityObject(networkObject);
20802098
}
2081-
var networkObject = SpawnedObjects[deferredObjectEntry.NetworkObjectId];
2082-
// Local instance despawns the instance
2083-
OnDespawnObject(networkObject, deferredObjectEntry.DestroyGameObject);
2084-
DeferredDespawnObjects.Remove(deferredObjectEntry);
2099+
2100+
DeferredDespawnObjects.RemoveAt(i);
20852101
}
20862102
}
20872103

0 commit comments

Comments
 (0)