Skip to content

Commit 47ed2dd

Browse files
chore: Optimize NetworkObject access from NetworkBehaviour (#3687)
## Purpose of this PR Methods inside of properties are much less performant to access that fields or methods without properties. This PR focusses on: 1. Removing references to `NetworkBehaviour.NetworkObject` from within `NetworkBehaviour`. Instead using `m_NetworkObject` anywhere that will be called after the `NetworkBehaviour` is spawned. a. Ensure that the `NetworkObject` explicitly sets `m_NetworkManager` when it calculates whether or not a `NetworkBehaviour` belongs in it's `ChildNetworkBehaviours` list. 2. Simplifies the areas where the `NetworkBehaviour` lifecycle callbacks are invoked a. Simplifies the callsites around `NetworkBehaviour.OnLostOwnership` and `NetworkBehaviour.OnGainedOwnership` b. Reduces loop duplication around `NetworkBehaviour.OnNetworkSpawn` ### Jira ticket _Link to related jira ticket ([Use the smart commits](https://support.atlassian.com/bitbucket-cloud/docs/use-smart-commits/)). Short version (e.g. MTT-123) also works and gets auto-linked_ to be created. ### Changelog - Fixed: Improved `NetworkBehaviour` performance. ## Documentation - No documentation changes or additions were necessary. ## Testing & QA (How your changes can be verified during release Playtest) This change needs a full play of astroids, and potentially the OwnershipChanging demo. Everything here should be covered by the automated tests, but this is a hot enough codepath that playtesting is definitely needed here. ### Functional Testing [//]: # (If checked, List manual tests that have been performed.) _Manual testing :_ - [x] `Manual testing done` _Automated tests:_ - [x] `Covered by existing automated tests` - [ ] `Covered by new automated tests` _Does the change require QA team to:_ - [ ] `Review automated tests`? - [ ] `Execute manual tests`? - [ ] `Provide feedback about the PR`? If any boxes above are checked the QA team will be automatically added as a PR reviewer. ## Backports This is a pure performance improvement and so doesn't need a backport. --------- Co-authored-by: Noel Stephens <[email protected]>
1 parent c1725c2 commit 47ed2dd

File tree

11 files changed

+293
-271
lines changed

11 files changed

+293
-271
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

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

1515
### Changed
1616

17+
- Improved performance around the NetworkBehaviour component. (#3687)
1718

1819
### Deprecated
1920

com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs

Lines changed: 66 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ internal enum __RpcExecStage
7474
internal FastBufferWriter __beginSendServerRpc(uint rpcMethodId, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
7575
#pragma warning restore IDE1006 // restore naming rule violation check
7676
{
77+
if (m_NetworkObject == null && !IsSpawned)
78+
{
79+
throw new RpcException("The NetworkBehaviour must be spawned before calling this method.");
80+
}
81+
7782
return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize);
7883
}
7984

@@ -142,7 +147,7 @@ internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMeth
142147
{
143148
NetworkManager.NetworkMetrics.TrackRpcSent(
144149
NetworkManager.ServerClientId,
145-
NetworkObject,
150+
m_NetworkObject,
146151
rpcMethodName,
147152
__getTypeName(),
148153
rpcWriteSize);
@@ -155,6 +160,11 @@ internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMeth
155160
internal FastBufferWriter __beginSendClientRpc(uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
156161
#pragma warning restore IDE1006 // restore naming rule violation check
157162
{
163+
if (m_NetworkObject == null && !IsSpawned)
164+
{
165+
throw new RpcException("The NetworkBehaviour must be spawned before calling this method.");
166+
}
167+
158168
return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize);
159169
}
160170

@@ -206,7 +216,7 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth
206216
continue;
207217
}
208218
// Check to make sure we are sending to only observers, if not log an error.
209-
if (networkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId))
219+
if (networkManager.LogLevel >= LogLevel.Error && !m_NetworkObject.Observers.Contains(targetClientId))
210220
{
211221
NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId));
212222
}
@@ -223,7 +233,7 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth
223233
continue;
224234
}
225235
// Check to make sure we are sending to only observers, if not log an error.
226-
if (networkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId))
236+
if (networkManager.LogLevel >= LogLevel.Error && !m_NetworkObject.Observers.Contains(targetClientId))
227237
{
228238
NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId));
229239
}
@@ -232,7 +242,7 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth
232242
}
233243
else
234244
{
235-
var observerEnumerator = NetworkObject.Observers.GetEnumerator();
245+
var observerEnumerator = m_NetworkObject.Observers.GetEnumerator();
236246
while (observerEnumerator.MoveNext())
237247
{
238248
// Skip over the host
@@ -274,7 +284,7 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth
274284
{
275285
networkManager.NetworkMetrics.TrackRpcSent(
276286
targetClientId,
277-
NetworkObject,
287+
m_NetworkObject,
278288
rpcMethodName,
279289
__getTypeName(),
280290
rpcWriteSize);
@@ -286,20 +296,20 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth
286296
{
287297
networkManager.NetworkMetrics.TrackRpcSent(
288298
targetClientId,
289-
NetworkObject,
299+
m_NetworkObject,
290300
rpcMethodName,
291301
__getTypeName(),
292302
rpcWriteSize);
293303
}
294304
}
295305
else
296306
{
297-
var observerEnumerator = NetworkObject.Observers.GetEnumerator();
307+
var observerEnumerator = m_NetworkObject.Observers.GetEnumerator();
298308
while (observerEnumerator.MoveNext())
299309
{
300310
networkManager.NetworkMetrics.TrackRpcSent(
301311
observerEnumerator.Current,
302-
NetworkObject,
312+
m_NetworkObject,
303313
rpcMethodName,
304314
__getTypeName(),
305315
rpcWriteSize);
@@ -315,6 +325,10 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth
315325
internal FastBufferWriter __beginSendRpc(uint rpcMethodId, RpcParams rpcParams, RpcAttribute.RpcAttributeParams attributeParams, SendTo defaultTarget, RpcDelivery rpcDelivery)
316326
#pragma warning restore IDE1006 // restore naming rule violation check
317327
{
328+
if (m_NetworkObject == null && !IsSpawned)
329+
{
330+
throw new RpcException("The NetworkBehaviour must be spawned before calling this method.");
331+
}
318332
if (attributeParams.RequireOwnership && !IsOwner)
319333
{
320334
throw new RpcException("This RPC can only be sent by its owner.");
@@ -546,6 +560,11 @@ internal bool IsBehaviourEditable()
546560
((networkManager.DistributedAuthorityMode && m_NetworkObject.IsOwner) || (!networkManager.DistributedAuthorityMode && networkManager.IsServer));
547561
}
548562

563+
internal void SetNetworkObject(NetworkObject networkObject)
564+
{
565+
m_NetworkObject = networkObject;
566+
}
567+
549568
// TODO: this needs an overhaul. It's expensive, it's ja little naive in how it looks for networkObject in
550569
// its parent and worst, it creates a puzzle if you are a NetworkBehaviour wanting to see if you're live or not
551570
// (e.g. editor code). All you want to do is find out if NetworkManager is null, but to do that you
@@ -635,43 +654,32 @@ protected NetworkBehaviour GetNetworkBehaviour(ushort behaviourId)
635654
/// </summary>
636655
internal void UpdateNetworkProperties()
637656
{
638-
var networkObject = NetworkObject;
639-
// Set NetworkObject dependent properties
640-
if (networkObject != null)
641-
{
642-
var networkManager = NetworkManager;
643-
// Set identification related properties
644-
NetworkObjectId = networkObject.NetworkObjectId;
645-
IsLocalPlayer = networkObject.IsLocalPlayer;
646-
647-
// This is "OK" because GetNetworkBehaviourOrderIndex uses the order of
648-
// NetworkObject.ChildNetworkBehaviours which is set once when first
649-
// accessed.
650-
NetworkBehaviourId = networkObject.GetNetworkBehaviourOrderIndex(this);
651-
652-
// Set ownership related properties
653-
IsOwnedByServer = networkObject.IsOwnedByServer;
654-
IsOwner = networkObject.IsOwner;
655-
OwnerClientId = networkObject.OwnerClientId;
656-
657-
// Set NetworkManager dependent properties
658-
if (networkManager != null)
659-
{
660-
IsHost = networkManager.IsListening && networkManager.IsHost;
661-
IsClient = networkManager.IsListening && networkManager.IsClient;
662-
IsServer = networkManager.IsListening && networkManager.IsServer;
663-
LocalClient = networkManager.LocalClient;
664-
HasAuthority = networkObject.HasAuthority;
665-
ServerIsHost = networkManager.IsListening && networkManager.ServerIsHost;
666-
}
667-
}
668-
else // Shouldn't happen, but if so then set the properties to their default value;
657+
var networkObject = m_NetworkObject;
658+
var networkManager = NetworkManager;
659+
660+
// Set identification related properties
661+
NetworkObjectId = networkObject.NetworkObjectId;
662+
IsLocalPlayer = networkObject.IsLocalPlayer;
663+
664+
// This is "OK" because GetNetworkBehaviourOrderIndex uses the order of
665+
// NetworkObject.ChildNetworkBehaviours which is set once when first
666+
// accessed.
667+
NetworkBehaviourId = networkObject.GetNetworkBehaviourOrderIndex(this);
668+
669+
// Set ownership related properties
670+
IsOwnedByServer = networkObject.IsOwnedByServer;
671+
IsOwner = networkObject.IsOwner;
672+
OwnerClientId = networkObject.OwnerClientId;
673+
674+
// Set NetworkManager dependent properties
675+
if (networkManager != null)
669676
{
670-
OwnerClientId = NetworkObjectId = default;
671-
IsOwnedByServer = IsOwner = IsHost = IsClient = IsServer = ServerIsHost = default;
672-
NetworkBehaviourId = default;
673-
LocalClient = default;
674-
HasAuthority = default;
677+
IsHost = networkManager.IsListening && networkManager.IsHost;
678+
IsClient = networkManager.IsListening && networkManager.IsClient;
679+
IsServer = networkManager.IsListening && networkManager.IsServer;
680+
LocalClient = networkManager.LocalClient;
681+
HasAuthority = networkObject.HasAuthority;
682+
ServerIsHost = networkManager.IsListening && networkManager.ServerIsHost;
675683
}
676684
}
677685

@@ -752,8 +760,11 @@ public virtual void OnNetworkDespawn() { }
752760
/// </summary>
753761
public virtual void OnNetworkPreDespawn() { }
754762

755-
internal void NetworkPreSpawn(ref NetworkManager networkManager)
763+
internal void NetworkPreSpawn(ref NetworkManager networkManager, NetworkObject networkObject)
756764
{
765+
m_NetworkObject = networkObject;
766+
UpdateNetworkProperties();
767+
757768
try
758769
{
759770
OnNetworkPreSpawn(ref networkManager);
@@ -767,12 +778,10 @@ internal void NetworkPreSpawn(ref NetworkManager networkManager)
767778
internal void InternalOnNetworkSpawn()
768779
{
769780
IsSpawned = true;
781+
// Initialize the NetworkVariables so they are accessible in OnNetworkSpawn;
770782
InitializeVariables();
771783
UpdateNetworkProperties();
772-
}
773784

774-
internal void VisibleOnNetworkSpawn()
775-
{
776785
try
777786
{
778787
OnNetworkSpawn();
@@ -782,9 +791,10 @@ internal void VisibleOnNetworkSpawn()
782791
Debug.LogException(e);
783792
}
784793

794+
// Initialize again in case the user's OnNetworkSpawn changed something
785795
InitializeVariables();
786796

787-
if (NetworkObject.HasAuthority)
797+
if (m_NetworkObject.HasAuthority)
788798
{
789799
// Since we just spawned the object and since user code might have modified their NetworkVariable, esp.
790800
// NetworkList, we need to mark the object as free of updates.
@@ -872,11 +882,10 @@ public virtual void OnGainedOwnership() { }
872882

873883
internal void InternalOnGainedOwnership()
874884
{
875-
UpdateNetworkProperties();
876885
// New owners need to assure any NetworkVariables they have write permissions
877886
// to are updated so the previous and original values are aligned with the
878887
// current value (primarily for collections).
879-
if (OwnerClientId == NetworkManager.LocalClientId)
888+
if (IsOwner)
880889
{
881890
UpdateNetworkVariableOnOwnershipChanged();
882891
}
@@ -907,12 +916,6 @@ internal void InternalOnOwnershipChanged(ulong previous, ulong current)
907916
/// </summary>
908917
public virtual void OnLostOwnership() { }
909918

910-
internal void InternalOnLostOwnership()
911-
{
912-
UpdateNetworkProperties();
913-
OnLostOwnership();
914-
}
915-
916919
/// <summary>
917920
/// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed.
918921
/// </summary>
@@ -1104,7 +1107,7 @@ internal void NetworkVariableUpdate(ulong targetClientId, bool forceSend = false
11041107

11051108
// Getting these ahead of time actually improves performance
11061109
var networkManager = NetworkManager;
1107-
var networkObject = NetworkObject;
1110+
var networkObject = m_NetworkObject;
11081111
var behaviourIndex = networkObject.GetNetworkBehaviourOrderIndex(this);
11091112
var messageManager = networkManager.MessageManager;
11101113
var connectionManager = networkManager.ConnectionManager;
@@ -1190,7 +1193,7 @@ private bool CouldHaveDirtyNetworkVariables()
11901193
}
11911194
// If it's dirty but can't be sent yet, we have to keep monitoring it until one of the
11921195
// conditions blocking its send changes.
1193-
NetworkManager.BehaviourUpdater.AddForUpdate(NetworkObject);
1196+
NetworkManager.BehaviourUpdater.AddForUpdate(m_NetworkObject);
11941197
}
11951198
}
11961199

@@ -1551,12 +1554,12 @@ internal virtual void InternalOnDestroy()
15511554
public virtual void OnDestroy()
15521555
{
15531556
InternalOnDestroy();
1554-
if (NetworkObject != null && NetworkObject.IsSpawned && IsSpawned)
1557+
if (m_NetworkObject != null && m_NetworkObject.IsSpawned && IsSpawned)
15551558
{
15561559
// If the associated NetworkObject is still spawned then this
15571560
// NetworkBehaviour will be removed from the NetworkObject's
15581561
// ChildNetworkBehaviours list.
1559-
NetworkObject.OnNetworkBehaviourDestroyed(this);
1562+
m_NetworkObject.OnNetworkBehaviourDestroyed(this);
15601563
}
15611564

15621565
// this seems odd to do here, but in fact especially in tests we can find ourselves
@@ -1575,6 +1578,8 @@ public virtual void OnDestroy()
15751578
{
15761579
NetworkVariableFields[i].Dispose();
15771580
}
1581+
1582+
m_NetworkObject = null;
15781583
}
15791584
}
15801585
}

0 commit comments

Comments
 (0)