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[] { }); } } }