diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md
index b785b6cae0..b3b1ccf467 100644
--- a/com.unity.netcode.gameobjects/CHANGELOG.md
+++ b/com.unity.netcode.gameobjects/CHANGELOG.md
@@ -13,6 +13,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Changed
+- Improve handling of destroyed NetworkBehaviours. (#3953)
- Hardened error handling and recovery during `NetworkObject` spawn. (#3941)
- Replaced Debug usage by NetcodeLog on `NetworkSpawnManager` and `NetworkObject`. (#3933)
- Improved performance of `NetworkBehaviour`. (#3915)
diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
index 14f8ea5dfb..59f9e637b6 100644
--- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
@@ -379,41 +379,21 @@ internal void __endSendRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId,
if (rpcParams.Send.Target == null)
{
- switch (defaultTarget)
+ rpcParams.Send.Target = defaultTarget switch
{
- case SendTo.Everyone:
- rpcParams.Send.Target = RpcTarget.Everyone;
- break;
- case SendTo.Owner:
- rpcParams.Send.Target = RpcTarget.Owner;
- break;
- case SendTo.Server:
- rpcParams.Send.Target = RpcTarget.Server;
- break;
- case SendTo.NotServer:
- rpcParams.Send.Target = RpcTarget.NotServer;
- break;
- case SendTo.NotMe:
- rpcParams.Send.Target = RpcTarget.NotMe;
- break;
- case SendTo.NotOwner:
- rpcParams.Send.Target = RpcTarget.NotOwner;
- break;
- case SendTo.Me:
- rpcParams.Send.Target = RpcTarget.Me;
- break;
- case SendTo.ClientsAndHost:
- rpcParams.Send.Target = RpcTarget.ClientsAndHost;
- break;
- case SendTo.Authority:
- rpcParams.Send.Target = RpcTarget.Authority;
- break;
- case SendTo.NotAuthority:
- rpcParams.Send.Target = RpcTarget.NotAuthority;
- break;
- case SendTo.SpecifiedInParams:
- throw new RpcException("This method requires a runtime-specified send target.");
- }
+ SendTo.Everyone => RpcTarget.Everyone,
+ SendTo.Owner => RpcTarget.Owner,
+ SendTo.Server => RpcTarget.Server,
+ SendTo.NotServer => RpcTarget.NotServer,
+ SendTo.NotMe => RpcTarget.NotMe,
+ SendTo.NotOwner => RpcTarget.NotOwner,
+ SendTo.Me => RpcTarget.Me,
+ SendTo.ClientsAndHost => RpcTarget.ClientsAndHost,
+ SendTo.Authority => RpcTarget.Authority,
+ SendTo.NotAuthority => RpcTarget.NotAuthority,
+ SendTo.SpecifiedInParams => throw new RpcException("This method requires a runtime-specified send target."),
+ _ => throw new RpcException("This method requires a runtime-specified send target."),
+ };
}
else if (defaultTarget != SendTo.SpecifiedInParams && !attributeParams.AllowTargetOverride)
{
@@ -473,15 +453,8 @@ public NetworkManager NetworkManager
#pragma warning disable IDE0001
///
/// Provides access to the various targets at runtime, as well as
- /// runtime-bound targets like ,
- /// ,
- /// ,
- /// ,
- /// , ,
- /// ,
- /// ,
- /// , and
- /// .
+ /// runtime-bound targets like , , and
+ /// .
///
#pragma warning restore IDE0001
public RpcTarget RpcTarget { get; private set; }
@@ -623,11 +596,6 @@ public NetworkObject NetworkObject
///
public ushort NetworkBehaviourId { get; internal set; }
- ///
- /// Internally caches the Id of this behaviour in a NetworkObject. Makes look-up faster
- ///
- internal ushort NetworkBehaviourIdCache = 0;
-
///
/// Returns the NetworkBehaviour with a given BehaviourId for the current NetworkObject.
///
@@ -789,7 +757,7 @@ internal virtual void InternalOnNetworkPreSpawn(ref NetworkManager networkManage
///
/// Handles pre-spawn related initializations.
/// Invokes any subscriptions.
- /// Finally invokes .
+ /// Finally invokes .
///
internal void NetworkPreSpawn(ref NetworkManager networkManager, NetworkObject networkObject)
{
@@ -1137,14 +1105,14 @@ internal void InitializeVariables()
// placed NetworkObject in an already loaded scene that has already been
// used within a network session =or= if this is a pooled NetworkObject
// that is being repurposed.
- for (int i = 0; i < NetworkVariableFields.Count; i++)
+ foreach (var variable in NetworkVariableFields)
{
// If already initialized, then skip
- if (NetworkVariableFields[i].HasBeenInitialized)
+ if (variable.HasBeenInitialized)
{
continue;
}
- NetworkVariableFields[i].Initialize(this);
+ variable.Initialize(this);
}
// Exit early as we don't need to run through the rest of this initialization
// process
@@ -1172,9 +1140,8 @@ internal void InitializeVariables()
for (int i = 0; i < NetworkVariableFields.Count; i++)
{
var networkDelivery = MessageDeliveryType.DefaultDelivery;
- if (!firstLevelIndex.ContainsKey(networkDelivery))
+ if (firstLevelIndex.TryAdd(networkDelivery, secondLevelCounter))
{
- firstLevelIndex.Add(networkDelivery, secondLevelCounter);
m_DeliveryTypesForNetworkVariableGroups.Add(networkDelivery);
secondLevelCounter++;
}
@@ -1202,9 +1169,8 @@ internal void PostNetworkVariableWrite(bool forced = false)
{
// Mark every variable as no longer dirty. We just spawned the object and whatever the game code did
// during OnNetworkSpawn has been sent and needs to be cleared
- for (int i = 0; i < NetworkVariableFields.Count; i++)
+ foreach (var networkVariable in NetworkVariableFields)
{
- var networkVariable = NetworkVariableFields[i];
if (networkVariable.IsDirty())
{
if (networkVariable.CanSend())
@@ -1338,9 +1304,8 @@ internal void NetworkVariableUpdate(ulong targetClientId, bool forceSend = false
private bool CouldHaveDirtyNetworkVariables()
{
// TODO: There should be a better way by reading one dirty variable vs. 'n'
- for (int i = 0; i < NetworkVariableFields.Count; i++)
+ foreach (var networkVariable in NetworkVariableFields)
{
- var networkVariable = NetworkVariableFields[i];
if (networkVariable.IsDirty())
{
if (networkVariable.CanSend())
@@ -1366,12 +1331,12 @@ private bool CouldHaveDirtyNetworkVariables()
///
internal void UpdateNetworkVariableOnOwnershipChanged()
{
- for (int j = 0; j < NetworkVariableFields.Count; j++)
+ foreach (var variable in NetworkVariableFields)
{
// Only invoke OnInitialize on NetworkVariables the owner can write to
- if (NetworkVariableFields[j].CanClientWrite(OwnerClientId))
+ if (variable.CanClientWrite(OwnerClientId))
{
- NetworkVariableFields[j].OnInitialize();
+ variable.OnInitialize();
}
}
}
@@ -1391,15 +1356,15 @@ internal void MarkVariablesDirty(bool dirty)
///
internal void MarkOwnerReadDirtyAndCheckOwnerWriteIsDirty()
{
- for (int j = 0; j < NetworkVariableFields.Count; j++)
+ foreach (var variable in NetworkVariableFields)
{
- if (NetworkVariableFields[j].ReadPerm == NetworkVariableReadPermission.Owner)
+ if (variable.ReadPerm == NetworkVariableReadPermission.Owner)
{
- NetworkVariableFields[j].SetDirty(true);
+ variable.SetDirty(true);
}
- if (NetworkVariableFields[j].WritePerm == NetworkVariableWritePermission.Owner)
+ if (variable.WritePerm == NetworkVariableWritePermission.Owner)
{
- NetworkVariableFields[j].OnCheckIsDirtyState();
+ variable.OnCheckIsDirtyState();
}
}
}
diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs
index 82219deba4..4f12bca033 100644
--- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs
@@ -56,9 +56,9 @@ internal void ProcessDirtyObjectServer(NetworkObject dirtyObj, bool forceSend)
if (m_NetworkManager.DistributedAuthorityMode || dirtyObj.IsNetworkVisibleTo(client.ClientId))
{
// Sync just the variables for just the objects this client sees
- for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
+ foreach (var behaviour in dirtyObj.ChildNetworkBehaviours.Values)
{
- dirtyObj.ChildNetworkBehaviours[k].NetworkVariableUpdate(client.ClientId, forceSend);
+ behaviour.NetworkVariableUpdate(client.ClientId, forceSend);
}
}
}
@@ -73,9 +73,9 @@ internal void ProcessDirtyObjectServer(NetworkObject dirtyObj, bool forceSend)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ProcessDirtyObjectClient(NetworkObject dirtyObj, bool forceSend)
{
- for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
+ foreach (var behaviour in dirtyObj.ChildNetworkBehaviours.Values)
{
- dirtyObj.ChildNetworkBehaviours[k].NetworkVariableUpdate(NetworkManager.ServerClientId, forceSend);
+ behaviour.NetworkVariableUpdate(NetworkManager.ServerClientId, forceSend);
}
}
@@ -86,9 +86,8 @@ internal void ProcessDirtyObjectClient(NetworkObject dirtyObj, bool forceSend)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void PostProcessDirtyObject(NetworkObject dirtyObj)
{
- for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
+ foreach (var behaviour in dirtyObj.ChildNetworkBehaviours.Values)
{
- var behaviour = dirtyObj.ChildNetworkBehaviours[k];
for (int i = 0; i < behaviour.NetworkVariableFields.Count; i++)
{
// Set to true for NetworkVariable to ignore duplication of the
@@ -114,7 +113,7 @@ internal void PostProcessDirtyObject(NetworkObject dirtyObj)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ResetDirtyObject(NetworkObject dirtyObj, bool forceSend)
{
- foreach (var behaviour in dirtyObj.ChildNetworkBehaviours)
+ foreach (var behaviour in dirtyObj.ChildNetworkBehaviours.Values)
{
behaviour.PostNetworkVariableWrite(forceSend);
}
@@ -164,9 +163,9 @@ internal void ProcessDirtyObject(NetworkObject networkObject, bool forceSend)
}
// Pre-variable update
- for (int k = 0; k < networkObject.ChildNetworkBehaviours.Count; k++)
+ foreach (var behaviour in networkObject.ChildNetworkBehaviours.Values)
{
- networkObject.ChildNetworkBehaviours[k].PreVariableUpdate();
+ behaviour.PreVariableUpdate();
}
// Server sends updates to all clients where a client sends updates
diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
index 335fec109f..d2f5aa5869 100644
--- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
@@ -426,36 +426,37 @@ public void DeferDespawn(int tickOffset, bool destroy = true)
var connectionManager = NetworkManagerOwner.ConnectionManager;
- for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
+ foreach (var behaviour in ChildNetworkBehaviours.Values)
{
- ChildNetworkBehaviours[i].PreVariableUpdate();
+ behaviour.PreVariableUpdate();
// Notify all NetworkBehaviours that the authority is performing a deferred despawn.
// This is when user script would update NetworkVariable states that might be needed
// for the deferred despawn sequence on non-authoritative instances.
- ChildNetworkBehaviours[i].OnDeferringDespawn(DeferredDespawnTick);
+ behaviour.OnDeferringDespawn(DeferredDespawnTick);
}
// DAHost handles sending updates to all clients
if (NetworkManagerOwner.DAHost)
{
- for (int i = 0; i < connectionManager.ConnectedClientsList.Count; i++)
+ foreach (var client in connectionManager.ConnectedClientsList)
{
- var client = connectionManager.ConnectedClientsList[i];
- if (IsNetworkVisibleTo(client.ClientId))
+ if (!IsNetworkVisibleTo(client.ClientId))
{
- // Sync just the variables for just the objects this client sees
- for (int k = 0; k < ChildNetworkBehaviours.Count; k++)
- {
- ChildNetworkBehaviours[k].NetworkVariableUpdate(client.ClientId);
- }
+ continue;
+ }
+
+ // Sync just the variables for just the objects this client sees
+ foreach (var behaviour in ChildNetworkBehaviours.Values)
+ {
+ behaviour.NetworkVariableUpdate(client.ClientId);
}
}
}
else // Clients just send their deltas to the service or DAHost
{
- for (int k = 0; k < ChildNetworkBehaviours.Count; k++)
+ foreach (var behaviour in ChildNetworkBehaviours.Values)
{
- ChildNetworkBehaviours[k].NetworkVariableUpdate(NetworkManager.ServerClientId);
+ behaviour.NetworkVariableUpdate(NetworkManager.ServerClientId);
}
}
@@ -1710,20 +1711,15 @@ internal void SetIsDestroying()
return;
}
- if (m_ChildNetworkBehaviours != null)
+ if (ChildNetworkBehaviours != null)
{
- foreach (var childBehaviour in m_ChildNetworkBehaviours)
+ foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
- // Just ignore and continue processing through the entries
- if (!childBehaviour)
- {
- continue;
- }
-
+ // Tell the childBehaviour that this NetworkObject is being destroyed.
// Keeping the property a private set to assure this is
// the only way it can be set as it should never be reset
// back to false once invoked.
- childBehaviour.SetIsDestroying();
+ childBehaviour?.SetIsDestroying();
}
}
IsDestroying = true;
@@ -2039,7 +2035,7 @@ public void Despawn(bool destroy = true)
return;
}
- foreach (var behavior in ChildNetworkBehaviours)
+ foreach (var behavior in ChildNetworkBehaviours.Values)
{
behavior.MarkVariablesDirty(false);
}
@@ -2115,7 +2111,7 @@ internal void InvokeBehaviourOnOwnershipChanged(ulong originalOwnerClientId, ulo
NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, originalOwnerClientId, true);
}
- foreach (var childBehaviour in ChildNetworkBehaviours)
+ foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
childBehaviour.UpdateNetworkProperties();
if (distributedAuthorityMode || isServer || isPreviousOwner)
@@ -2128,7 +2124,7 @@ internal void InvokeBehaviourOnOwnershipChanged(ulong originalOwnerClientId, ulo
if (distributedAuthorityMode || isServer || isNewOwner)
{
- foreach (var childBehaviour in ChildNetworkBehaviours)
+ foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
if (!childBehaviour.gameObject.activeInHierarchy)
{
@@ -2145,17 +2141,17 @@ internal void InvokeBehaviourOnOwnershipChanged(ulong originalOwnerClientId, ulo
internal void InvokeOwnershipChanged(ulong previous, ulong next)
{
- for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
+ foreach (var child in ChildNetworkBehaviours.Values)
{
- if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
+ if (child.gameObject.activeInHierarchy)
{
- ChildNetworkBehaviours[i].InternalOnOwnershipChanged(previous, next);
+ child.InternalOnOwnershipChanged(previous, next);
}
else
{
if (NetworkManagerOwner.LogLevel <= LogLevel.Normal)
{
- NetworkLog.LogWarning($"[{name}] {ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during ownership assignment!");
+ NetworkLog.LogWarning($"[{name}] {child.gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {child.GetType().Name} component was skipped during ownership assignment!");
}
}
}
@@ -2168,7 +2164,7 @@ internal void InvokeSessionOwnerPromoted(bool isSessionOwner)
return;
}
- foreach (var childBehaviour in ChildNetworkBehaviours)
+ foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
childBehaviour.IsSessionOwner = isSessionOwner;
}
@@ -2176,18 +2172,22 @@ internal void InvokeSessionOwnerPromoted(bool isSessionOwner)
internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNetworkObject)
{
- for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
+ if (ChildNetworkBehaviours == null)
+ {
+ InitializeChildNetworkBehaviours();
+ }
+ foreach (var child in ChildNetworkBehaviours.Values)
{
// Any NetworkBehaviour that is not spawned and the associated GameObject is disabled should be
// skipped over (i.e. not supported).
- if (!ChildNetworkBehaviours[i].IsSpawned && !ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
+ if (!child.IsSpawned && !child.gameObject.activeInHierarchy)
{
continue;
}
// Invoke internal notification
- ChildNetworkBehaviours[i].InternalOnNetworkObjectParentChanged(parentNetworkObject);
+ child.InternalOnNetworkObjectParentChanged(parentNetworkObject);
// Invoke public notification
- ChildNetworkBehaviours[i].OnNetworkObjectParentChanged(parentNetworkObject);
+ child.OnNetworkObjectParentChanged(parentNetworkObject);
}
}
@@ -2635,9 +2635,10 @@ internal static void CheckOrphanChildren()
internal void InvokeBehaviourNetworkPreSpawn()
{
var networkManager = NetworkManager;
- for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
+ InitializeChildNetworkBehaviours();
+ foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
- ChildNetworkBehaviours[i].NetworkPreSpawn(ref networkManager, this);
+ childBehaviour.NetworkPreSpawn(ref networkManager, this);
}
}
@@ -2651,7 +2652,7 @@ internal void InvokeBehaviourNetworkSpawn()
// prior to invoking OnNetworkSpawn so cross NetworkBehaviour:
// - accessing of NetworkVariables will work correctly.
// - invocation of RPCs will work properly (and not throw exception under certain scenarios)
- foreach (var childBehaviour in ChildNetworkBehaviours)
+ foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
if (!childBehaviour.gameObject.activeInHierarchy)
{
@@ -2665,7 +2666,7 @@ internal void InvokeBehaviourNetworkSpawn()
}
// After initialization, we can then invoke OnNetworkSpawn on each child NetworkBehaviour.
- foreach (var childBehaviour in ChildNetworkBehaviours)
+ foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
if (!childBehaviour.gameObject.activeInHierarchy)
{
@@ -2681,111 +2682,106 @@ internal void InvokeBehaviourNetworkSpawn()
internal void InvokeBehaviourNetworkPostSpawn()
{
- for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
+ foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
- if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
+ if (childBehaviour.gameObject.activeInHierarchy)
{
- ChildNetworkBehaviours[i].NetworkPostSpawn();
+ childBehaviour.NetworkPostSpawn();
}
}
}
internal void InternalNetworkSessionSynchronized()
{
- for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
+ foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
- if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
+ if (childBehaviour.gameObject.activeInHierarchy)
{
- ChildNetworkBehaviours[i].NetworkSessionSynchronized();
+ childBehaviour.NetworkSessionSynchronized();
}
}
}
internal void InternalInSceneNetworkObjectsSpawned()
{
- for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
+ foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
- if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
+ if (childBehaviour.gameObject.activeInHierarchy)
{
- ChildNetworkBehaviours[i].InSceneNetworkObjectsSpawned();
+ childBehaviour.InSceneNetworkObjectsSpawned();
}
}
}
internal void InvokeBehaviourNetworkDespawn()
{
+ if (ChildNetworkBehaviours == null)
+ {
+ InitializeChildNetworkBehaviours();
+ }
// Invoke OnNetworkPreDespawn on all child behaviours
- for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
+ foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
- ChildNetworkBehaviours[i].InternalOnNetworkPreDespawn();
+ childBehaviour.InternalOnNetworkPreDespawn();
}
NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true);
NetworkManagerOwner.SpawnManager.RemoveNetworkObjectFromSceneChangedUpdates(this);
- for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
+ foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
- ChildNetworkBehaviours[i].InternalOnNetworkDespawn();
+ childBehaviour.InternalOnNetworkDespawn();
}
}
- private List m_ChildNetworkBehaviours;
-
internal string GenerateDisabledNetworkBehaviourWarning(NetworkBehaviour networkBehaviour)
{
return $"[{name}][{networkBehaviour.GetType().Name}][{nameof(isActiveAndEnabled)}: {networkBehaviour.isActiveAndEnabled}] Disabled {nameof(NetworkBehaviour)}s will be excluded from spawning and synchronization!";
}
- internal List ChildNetworkBehaviours
+ internal Dictionary ChildNetworkBehaviours;
+ internal bool InitializeChildNetworkBehaviours()
{
- get
+ ChildNetworkBehaviours = new Dictionary();
+ NetworkTransforms = new List();
+#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
+ NetworkRigidbodies = new List();
+#endif
+
+ var networkBehaviours = GetComponentsInChildren(true);
+ foreach (var behaviour in networkBehaviours)
{
- if (m_ChildNetworkBehaviours != null)
+ // Find the first parent NetworkObject of this child
+ // if it's not ourselves, this childBehaviour belongs to a different NetworkObject.
+ var networkObj = behaviour.GetComponentInParent();
+ if (networkObj != this)
{
- return m_ChildNetworkBehaviours;
+ continue;
}
- m_ChildNetworkBehaviours = new List();
- var networkBehaviours = GetComponentsInChildren(true);
- for (int i = 0; i < networkBehaviours.Length; i++)
- {
- // Find the first parent NetworkObject of this child
- // if it's not ourselves, this childBehaviour belongs to a different NetworkObject.
- var networkObj = networkBehaviours[i].GetComponentInParent();
- if (networkObj != this)
- {
- continue;
- }
+ // Set ourselves as the NetworkObject that this behaviour belongs to and add it to the child list
+ var nextIndex = (ushort)ChildNetworkBehaviours.Count;
+ behaviour.SetNetworkObject(this, nextIndex);
+ ChildNetworkBehaviours.Add(nextIndex, behaviour);
- // Set ourselves as the NetworkObject that this behaviour belongs to and add it to the child list
- var nextIndex = (ushort)m_ChildNetworkBehaviours.Count;
- networkBehaviours[i].SetNetworkObject(this, nextIndex);
- m_ChildNetworkBehaviours.Add(networkBehaviours[i]);
+ var networkTransform = behaviour as NetworkTransform;
+ if (networkTransform != null)
+ {
+ networkTransform.IsNested = networkTransform.gameObject != gameObject;
+ NetworkTransforms.Add(networkTransform);
+ }
- var type = networkBehaviours[i].GetType();
- if (type == typeof(NetworkTransform) || type.IsInstanceOfType(typeof(NetworkTransform)) || type.IsSubclassOf(typeof(NetworkTransform)))
- {
- if (NetworkTransforms == null)
- {
- NetworkTransforms = new List();
- }
- var networkTransform = networkBehaviours[i] as NetworkTransform;
- networkTransform.IsNested = i != 0 && networkTransform.gameObject != gameObject;
- NetworkTransforms.Add(networkTransform);
- }
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
- else if (type.IsSubclassOf(typeof(NetworkRigidbodyBase)))
- {
- if (NetworkRigidbodies == null)
- {
- NetworkRigidbodies = new List();
- }
- NetworkRigidbodies.Add(networkBehaviours[i] as NetworkRigidbodyBase);
- }
-#endif
+
+ var rigidbodyBase = behaviour as NetworkRigidbodyBase;
+ if (rigidbodyBase != null)
+ {
+ NetworkRigidbodies.Add(behaviour as NetworkRigidbodyBase);
}
- return m_ChildNetworkBehaviours;
+#endif
}
+
+ return true;
}
///
@@ -2805,9 +2801,9 @@ internal void SynchronizeOwnerNetworkVariables(ulong originalOwnerId, ulong orig
var currentOwnerId = OwnerClientId;
OwnerClientId = originalOwnerId;
PreviousOwnerId = originalPreviousOwnerId;
- for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
+ foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
- ChildNetworkBehaviours[i].MarkOwnerReadDirtyAndCheckOwnerWriteIsDirty();
+ childBehaviour.MarkOwnerReadDirtyAndCheckOwnerWriteIsDirty();
}
// Now set the new owner and previous owner identifiers back to their original new values
@@ -2866,29 +2862,11 @@ internal static void VerifyParentingStatus()
///
public ushort GetNetworkBehaviourOrderIndex(NetworkBehaviour instance)
{
- // read the cached index, and verify it first
- if (instance.NetworkBehaviourIdCache < ChildNetworkBehaviours.Count)
+ if (ChildNetworkBehaviours == null)
{
- if (ChildNetworkBehaviours[instance.NetworkBehaviourIdCache] == instance)
- {
- return instance.NetworkBehaviourIdCache;
- }
-
- // invalid cached id reset
- instance.NetworkBehaviourIdCache = default;
- }
-
- for (ushort i = 0; i < ChildNetworkBehaviours.Count; i++)
- {
- if (ChildNetworkBehaviours[i] == instance)
- {
- // cache the id, for next query
- instance.NetworkBehaviourIdCache = i;
- return i;
- }
+ InitializeChildNetworkBehaviours();
}
-
- return 0;
+ return instance.NetworkBehaviourId;
}
///
@@ -2898,28 +2876,31 @@ public ushort GetNetworkBehaviourOrderIndex(NetworkBehaviour instance)
/// The at the ordered index value or null if it does not exist.
public NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index)
{
- if (index >= ChildNetworkBehaviours.Count)
+ if (ChildNetworkBehaviours == null)
{
- if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
- {
- NetworkLog.LogError($"{nameof(NetworkBehaviour)} index {index} was out of bounds for {name}. NetworkBehaviours must be the same, and in the same order, between server and client.");
- }
- if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
+ InitializeChildNetworkBehaviours();
+ }
+
+ if (ChildNetworkBehaviours.TryGetValue(index, out var childBehaviour))
+ {
+ return childBehaviour;
+ }
+
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
+ {
+ NetworkLog.LogError($"{nameof(NetworkBehaviour)} index {index} was out of bounds for {name}. NetworkBehaviours must be the same, and in the same order, between server and client.");
+ }
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
+ {
+ var currentKnownChildren = new StringBuilder();
+ currentKnownChildren.Append($"Known child {nameof(NetworkBehaviour)}s:");
+ foreach (var (id, behaviour) in ChildNetworkBehaviours)
{
- var currentKnownChildren = new StringBuilder();
- currentKnownChildren.Append($"Known child {nameof(NetworkBehaviour)}s:");
- for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
- {
- var childNetworkBehaviour = ChildNetworkBehaviours[i];
- currentKnownChildren.Append($" [{i}] {childNetworkBehaviour.__getTypeName()}");
- currentKnownChildren.Append(i < ChildNetworkBehaviours.Count - 1 ? "," : ".");
- }
- NetworkLog.LogInfo(currentKnownChildren.ToString());
+ currentKnownChildren.Append($"[{id}] {behaviour.__getTypeName()}, ");
}
- return null;
+ NetworkLog.LogInfo(currentKnownChildren.ToString());
}
-
- return ChildNetworkBehaviours[index];
+ return null;
}
///
@@ -3225,10 +3206,10 @@ internal void SynchronizeNetworkBehaviours(ref BufferSerializer serializer
var writer = serializer.GetFastBufferWriter();
// Synchronize NetworkVariables
- foreach (var behavior in ChildNetworkBehaviours)
+ foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
- behavior.InitializeVariables();
- behavior.WriteNetworkVariableData(writer, targetClientId);
+ childBehaviour.InitializeVariables();
+ childBehaviour.WriteNetworkVariableData(writer, targetClientId);
}
// Reserve the NetworkBehaviour synchronization count position
@@ -3239,7 +3220,7 @@ internal void SynchronizeNetworkBehaviours(ref BufferSerializer serializer
// had additional synchronization data written.
// (See notes for reading/deserialization below)
var synchronizationCount = (byte)0;
- foreach (var childBehaviour in ChildNetworkBehaviours)
+ foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
if (childBehaviour.Synchronize(ref serializer, targetClientId))
{
@@ -3260,7 +3241,7 @@ internal void SynchronizeNetworkBehaviours(ref BufferSerializer serializer
var reader = serializer.GetFastBufferReader();
// Apply the network variable synchronization data
- foreach (var behaviour in ChildNetworkBehaviours)
+ foreach (var behaviour in ChildNetworkBehaviours.Values)
{
behaviour.InitializeVariables();
behaviour.SetNetworkVariableData(reader, targetClientId);
@@ -3569,11 +3550,6 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false)
private void Awake()
{
- m_ChildNetworkBehaviours = null;
- NetworkTransforms?.Clear();
-#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
- NetworkRigidbodies?.Clear();
-#endif
SetCachedParent(transform.parent);
SceneOrigin = gameObject.scene;
}
@@ -3668,7 +3644,7 @@ internal void OnNetworkBehaviourDestroyed(NetworkBehaviour networkBehaviour)
{
NetworkLog.LogWarning($"{nameof(NetworkBehaviour)}-{networkBehaviour.name} is being destroyed while {nameof(NetworkObject)}-{name} is still spawned! (could break state synchronization)");
}
- ChildNetworkBehaviours.Remove(networkBehaviour);
+ ChildNetworkBehaviours.Remove(networkBehaviour.NetworkBehaviourId);
}
}
}
diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs
index 7497d54ab2..d475ed3a74 100644
--- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkTransformMessage.cs
@@ -70,11 +70,9 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int
return false;
}
var currentPosition = reader.Position;
- var networkObjectId = (ulong)0;
- var networkBehaviourId = 0;
- ByteUnpacker.ReadValueBitPacked(reader, out networkObjectId);
- var isSpawnedLocally = networkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId);
+ ByteUnpacker.ReadValueBitPacked(reader, out ulong networkObjectId);
+ var isSpawnedLocally = networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var networkObject);
// Only defer if the NetworkObject is not spawned yet and the local NetworkManager is not running as a DAHost.
if (!isSpawnedLocally && !networkManager.DAHost)
@@ -86,24 +84,22 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int
// While the below check and assignment might seem out of place, this is specific to running in DAHost mode when a NetworkObject is
// hidden from the DAHost but is visible to other clients. Since the DAHost needs to forward updates to the clients, we ignore processing
// this message locally
- var networkObject = (NetworkObject)null;
var isServerAuthoritative = false;
var ownerAuthoritativeServerSide = false;
// Get the behaviour index
- ByteUnpacker.ReadValueBitPacked(reader, out networkBehaviourId);
+ ByteUnpacker.ReadValueBitPacked(reader, out int networkBehaviourId);
if (isSpawnedLocally)
{
- networkObject = networkManager.SpawnManager.SpawnedObjects[networkObjectId];
- if (networkObject.ChildNetworkBehaviours.Count <= networkBehaviourId || networkObject.ChildNetworkBehaviours[networkBehaviourId] == null)
+ if (!networkObject.ChildNetworkBehaviours.TryGetValue((ushort)networkBehaviourId, out var behaviour) || behaviour == null)
{
Debug.LogError($"[{nameof(NetworkTransformMessage)}][Invalid] Targeted {nameof(NetworkTransform)}, {nameof(NetworkBehaviour.NetworkBehaviourId)} ({networkBehaviourId}), does not exist! Make sure you are not spawning {nameof(NetworkObject)}s with disabled {nameof(GameObject)}s that have {nameof(NetworkBehaviour)} components on them.");
return false;
}
// Get the target NetworkTransform
- var transform = networkObject.ChildNetworkBehaviours[networkBehaviourId] as NetworkTransform;
+ var transform = behaviour as NetworkTransform;
if (transform == null)
{
Debug.LogError($"[{nameof(NetworkTransformMessage)}][Invalid] Targeted {nameof(NetworkTransform)}, {nameof(NetworkBehaviour.NetworkBehaviourId)} ({networkBehaviourId}), does not exist on {networkObject.name}! Make sure you are not spawning {nameof(NetworkObject)}s with disabled {nameof(GameObject)}s that have {nameof(NetworkBehaviour)} components on them.");
diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
index 543aa15912..97eee27ae6 100644
--- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
@@ -487,9 +487,9 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool
// then notify the user they could potentially lose state updates if developer logging is enabled.
if (NetworkManager.LogLevel == LogLevel.Developer && !distributedAuthorityMode && m_LastChangeInOwnership.ContainsKey(networkObject.NetworkObjectId) && m_LastChangeInOwnership[networkObject.NetworkObjectId] > Time.realtimeSinceStartup)
{
- for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++)
+ foreach (var behaviour in networkObject.ChildNetworkBehaviours.Values)
{
- if (networkObject.ChildNetworkBehaviours[i].NetworkVariableFields.Count > 0)
+ if (behaviour.NetworkVariableFields.Count > 0)
{
NetworkLog.LogWarningServer($"[Rapid Ownership Change Detected][Potential Loss in State] Detected a rapid change in ownership that exceeds a frequency less than {k_MaximumTickOwnershipChangeMultiplier}x the current network tick rate! Provide at least {k_MaximumTickOwnershipChangeMultiplier}x the current network tick rate between ownership changes to avoid NetworkVariable state loss.");
break;
diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs
index b61afd40b7..abf0dbd60a 100644
--- a/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs
@@ -67,7 +67,7 @@ public void ProcessReanticipation()
var lastRoundTripTime = m_NetworkManager.LocalTime.Time - LastAnticipationAckTime;
foreach (var item in ObjectsToReanticipate)
{
- foreach (var behaviour in item.OwnerObject.ChildNetworkBehaviours)
+ foreach (var behaviour in item.OwnerObject.ChildNetworkBehaviours.Values)
{
behaviour.OnReanticipate(lastRoundTripTime);
}
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs
index 64dbc532be..e4f127564a 100644
--- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs
@@ -33,17 +33,14 @@ public void NetworkManagerOverrideTest()
}
[Test]
- [TestCase(0)]
- [TestCase(1)]
- [TestCase(2)]
- public void GetBehaviourIndexNone(int index)
+ public void GetBehaviourIndexNone()
{
var gameObject = new GameObject(nameof(GetBehaviourIndexNone));
var networkObject = gameObject.AddComponent();
LogAssert.Expect(LogType.Error, new Regex(".*out of bounds.*"));
- Assert.That(networkObject.GetNetworkBehaviourAtOrderIndex((ushort)index), Is.Null);
+ Assert.That(networkObject.GetNetworkBehaviourAtOrderIndex(5), Is.Null);
// Cleanup
Object.DestroyImmediate(gameObject);
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs
index 7398dd12dc..49ae47bec9 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformOwnershipTests.cs
@@ -656,6 +656,7 @@ protected override void OnCreatePlayerPrefab()
private void RandomizeObjectTransformPositions(GameObject gameObject)
{
var networkObject = gameObject.GetComponent();
+ networkObject.InitializeChildNetworkBehaviours();
Assert.True(networkObject.ChildNetworkBehaviours.Count > 0);
foreach (var networkTransform in networkObject.NetworkTransforms)
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/Components/ObjectNameIdentifier.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/Components/ObjectNameIdentifier.cs
index 17978b49ec..e33b6ea313 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/Components/ObjectNameIdentifier.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/Components/ObjectNameIdentifier.cs
@@ -106,13 +106,6 @@ public override void OnDestroy()
if (m_NetworkObject != null)
{
DeRegisterNetworkObject();
- // This is required otherwise it will try to continue to update the NetworkBehaviour even if
- // it has been destroyed (most likely integration test specific)
- if (m_NetworkObject.ChildNetworkBehaviours != null && m_NetworkObject.ChildNetworkBehaviours.Contains(this))
- {
- NetworkObject.ChildNetworkBehaviours.Remove(this);
- }
- m_NetworkObject = null;
}
base.OnDestroy();
}
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs
index 1253371448..7d2fa06284 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs
@@ -2650,11 +2650,12 @@ public static void SimulateOneFrame()
{
var method = obj.GetType().GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
method?.Invoke(obj, new object[] { });
- foreach (var behaviour in obj.ChildNetworkBehaviours)
- {
- var behaviourMethod = behaviour.GetType().GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
- behaviourMethod?.Invoke(behaviour, new object[] { });
- }
+ }
+ var networkBehaviours = FindObjects.ByType();
+ foreach (var behaviour in networkBehaviours)
+ {
+ var behaviourMethod = behaviour.GetType().GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+ behaviourMethod?.Invoke(behaviour, new object[] { });
}
}
}