diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 36cfd1f7a2..bde8409ece 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -24,6 +24,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where invoking an RPC, on another `NetworkBehaviour` associated with the same `NetworkObject` that is ordered before the `NetworkBehaviour` invoking the RPC, during `OnNetworkSpawn` could throw an exception if scene management is disabled. (#3782) - Fixed issue where the `Axis to Synchronize` toggles didn't work with multi object editing in `NetworkTransform`. (#3781) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 4749012fd3..620140ea02 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -595,9 +595,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, private const string k_NetworkManager_IsServer = nameof(NetworkManager.IsServer); private const string k_NetworkManager_IsClient = nameof(NetworkManager.IsClient); private const string k_NetworkManager_LogLevel = nameof(NetworkManager.LogLevel); - private const string k_NetworkBehaviour_rpc_func_table = nameof(NetworkBehaviour.__rpc_func_table); - private const string k_NetworkBehaviour_rpc_name_table = nameof(NetworkBehaviour.__rpc_name_table); private const string k_NetworkBehaviour_rpc_exec_stage = nameof(NetworkBehaviour.__rpc_exec_stage); private const string k_NetworkBehaviour_NetworkVariableFields = nameof(NetworkBehaviour.NetworkVariableFields); private const string k_NetworkBehaviour_beginSendServerRpc = nameof(NetworkBehaviour.__beginSendServerRpc); diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs index bdcc74e498..57295be52e 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs @@ -151,11 +151,12 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition) { fieldDefinition.IsFamilyOrAssembly = true; } - +#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_name_table)) { fieldDefinition.IsFamilyOrAssembly = true; } +#endif } foreach (var methodDefinition in typeDefinition.Methods) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/Unity.Netcode.Editor.CodeGen.asmdef b/com.unity.netcode.gameobjects/Editor/CodeGen/Unity.Netcode.Editor.CodeGen.asmdef index 690cd4bf5e..1accd5e5d7 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/Unity.Netcode.Editor.CodeGen.asmdef +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/Unity.Netcode.Editor.CodeGen.asmdef @@ -24,6 +24,11 @@ "name": "com.unity.nuget.mono-cecil", "expression": "(0,1.11.4)", "define": "CECIL_CONSTRAINTS_ARE_TYPE_REFERENCES" + }, + { + "name": "com.unity.multiplayer.tools", + "expression": "", + "define": "MULTIPLAYER_TOOLS" } ], "noEngineReferences": false diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 60b92ed4f4..af911b4385 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using Unity.Collections; using UnityEngine; @@ -40,7 +41,7 @@ public abstract class NetworkBehaviour : MonoBehaviour internal static readonly Dictionary> __rpc_func_table = new Dictionary>(); internal static readonly Dictionary> __rpc_permission_table = new Dictionary>(); -#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE +#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) // RuntimeAccessModifiersILPP will make this `public` internal static readonly Dictionary> __rpc_name_table = new Dictionary>(); #endif @@ -142,16 +143,9 @@ internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMeth } bufferWriter.Dispose(); -#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE - if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName)) - { - networkManager.NetworkMetrics.TrackRpcSent( - NetworkManager.ServerClientId, - m_NetworkObject, - rpcMethodName, - __getTypeName(), - rpcWriteSize); - } + +#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) + TrackRpcMetricsSend(ref serverRpcMessage, rpcMethodId, rpcWriteSize); #endif } @@ -275,7 +269,11 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth } bufferWriter.Dispose(); -#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE +#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) + if (!ValidateRpcMessageMetrics(GetType())) + { + return; + } if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName)) { if (clientRpcParams.Send.TargetClientIds != null) @@ -755,6 +753,11 @@ public virtual void OnNetworkPreDespawn() { } internal virtual void InternalOnNetworkPreSpawn(ref NetworkManager networkManager) { } + /// + /// Handles pre-spawn related initializations. + /// Invokes any subscriptions. + /// Finally invokes . + /// internal void NetworkPreSpawn(ref NetworkManager networkManager, NetworkObject networkObject) { m_NetworkObject = networkObject; @@ -782,13 +785,28 @@ internal void NetworkPreSpawn(ref NetworkManager networkManager, NetworkObject n } } + /// + /// Initializes the: + /// - state. + /// - instances. + /// - Spawned related properties are applied. + /// !! Note !!: + /// This also populates RPC related tables based on this 's RPCs (if any). + /// internal void InternalOnNetworkSpawn() { IsSpawned = true; - // Initialize the NetworkVariables so they are accessible in OnNetworkSpawn; + // Initialize the NetworkVariables and **RPC tables** so they are accessible in OnNetworkSpawn InitializeVariables(); + // Apply the spawned state/properties to this instance UpdateNetworkProperties(); + } + /// + /// Handles invoking . + /// + internal void NetworkSpawn() + { try { OnNetworkSpawn(); @@ -799,6 +817,9 @@ internal void InternalOnNetworkSpawn() } } + /// + /// Handles invoking . + /// internal void NetworkPostSpawn() { try @@ -819,6 +840,9 @@ internal void NetworkPostSpawn() } } + /// + /// Handles invoking . + /// internal void NetworkSessionSynchronized() { try @@ -832,6 +856,9 @@ internal void NetworkSessionSynchronized() } } + /// + /// Handles invoking . + /// internal void InSceneNetworkObjectsSpawned() { try @@ -844,6 +871,9 @@ internal void InSceneNetworkObjectsSpawned() } } + /// + /// Handles invoking . + /// internal void InternalOnNetworkPreDespawn() { try @@ -863,6 +893,9 @@ internal void InternalOnNetworkPreDespawn() } } + /// + /// Handles invoking . + /// internal void InternalOnNetworkDespawn() { IsSpawned = false; @@ -965,13 +998,90 @@ internal virtual void __initializeRpcs() internal void __registerRpc(uint hash, RpcReceiveHandler handler, string rpcMethodName, RpcInvokePermission permission) #pragma warning restore IDE1006 // restore naming rule violation check { - __rpc_func_table[GetType()][hash] = handler; - __rpc_permission_table[GetType()][hash] = permission; -#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE - __rpc_name_table[GetType()][hash] = rpcMethodName; + var rpcType = GetType(); + __rpc_func_table[rpcType][hash] = handler; + __rpc_permission_table[rpcType][hash] = permission; +#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) + __rpc_name_table[rpcType][hash] = rpcMethodName; #endif } +#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool ValidateRpcMessageMetrics(Type type) + { + if (m_NetworkManager == null) + { + Debug.LogError($"[{nameof(ValidateRpcMessageMetrics)}][{type.Name}] {nameof(NetworkBehaviour)} is attempting to invoking an RPC before {nameof(NetworkManager)} has been initialized!"); + return false; + } + + if (!__rpc_name_table.ContainsKey(type)) + { + Debug.LogError($"[{nameof(ValidateRpcMessageMetrics)}][{type.Name}][{nameof(__rpc_name_table)}] RPC table initialization failure: Table does not contain an entry for {type.Name}!"); + return false; + } + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void TrackRpcMetricsSend(ref ServerRpcMessage message, uint rpcMethodId, int rpcWriteSize) + { + var type = GetType(); + if (!ValidateRpcMessageMetrics(type)) + { + return; + } + if (__rpc_name_table[type].TryGetValue(rpcMethodId, out var rpcMethodName)) + { + m_NetworkManager.NetworkMetrics.TrackRpcSent( + NetworkManager.ServerClientId, + m_NetworkObject, + rpcMethodName, + __getTypeName(), + rpcWriteSize); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void TrackRpcMetricsSend(ulong clientId, ref RpcMessage message, int length) + { + var type = GetType(); + if (!ValidateRpcMessageMetrics(type)) + { + return; + } + if (__rpc_name_table[type].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) + { + m_NetworkManager.NetworkMetrics.TrackRpcSent( + m_NetworkManager.LocalClientId, + NetworkObject, + rpcMethodName, + __getTypeName(), + length); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void TrackRpcMetricsReceive(ref RpcMetadata metadata, ref NetworkContext context, int length) + { + var type = GetType(); + if (!ValidateRpcMessageMetrics(type)) + { + return; + } + if (__rpc_name_table[type].TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName)) + { + m_NetworkManager.NetworkMetrics.TrackRpcReceived( + context.SenderId, + NetworkObject, + rpcMethodName, + __getTypeName(), + length); + } + } +#endif + #pragma warning disable IDE1006 // disable naming rule violation check // RuntimeAccessModifiersILPP will make this `protected` // Using this method here because ILPP doesn't seem to let us do visibility modification on properties. @@ -1014,7 +1124,7 @@ internal void InitializeVariables() { __rpc_func_table[GetType()] = new Dictionary(); __rpc_permission_table[GetType()] = new Dictionary(); -#if UNITY_EDITOR || DEVELOPMENT_BUILD || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE +#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) __rpc_name_table[GetType()] = new Dictionary(); #endif __initializeRpcs(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 50a6304f52..1bbcf347a7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -49,10 +49,8 @@ public class NetworkManager : MonoBehaviour, INetworkUpdateSystem // RuntimeAccessModifiersILPP will make this `public` internal static readonly Dictionary __rpc_func_table = new Dictionary(); -#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE - // RuntimeAccessModifiersILPP will make this `public` + // RuntimeAccessModifiersILPP will make this `public` (legacy table should be removed in v3.x.x) internal static readonly Dictionary __rpc_name_table = new Dictionary(); -#endif #pragma warning restore IDE1006 // restore naming rule violation check diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index e29097426d..953781d008 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2556,6 +2556,12 @@ internal void InvokeBehaviourNetworkSpawn() { NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); + // Always invoke all InternalOnNetworkSpawn methods on each child NetworkBehaviour + // ** before ** invoking OnNetworkSpawn. + // This assures all NetworkVariables and RPC related tables have been initialized + // 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) { if (!childBehaviour.gameObject.activeInHierarchy) @@ -2565,6 +2571,17 @@ internal void InvokeBehaviourNetworkSpawn() } childBehaviour.InternalOnNetworkSpawn(); } + + // After initialization, we can then invoke OnNetworkSpawn on each child NetworkBehaviour. + foreach (var childBehaviour in ChildNetworkBehaviours) + { + if (!childBehaviour.gameObject.activeInHierarchy) + { + Debug.LogWarning($"{GenerateDisabledNetworkBehaviourWarning(childBehaviour)}"); + continue; + } + childBehaviour.NetworkSpawn(); + } } internal void InvokeBehaviourNetworkPostSpawn() @@ -2578,7 +2595,6 @@ internal void InvokeBehaviourNetworkPostSpawn() } } - internal void InternalNetworkSessionSynchronized() { for (int i = 0; i < ChildNetworkBehaviours.Count; i++) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index d37015fdcd..c8be0c251d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -40,17 +40,8 @@ public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkCo } payload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.None, reader.Length - reader.Position); - -#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE - if (NetworkBehaviour.__rpc_name_table[networkBehaviour.GetType()].TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName)) - { - networkManager.NetworkMetrics.TrackRpcReceived( - context.SenderId, - networkObject, - rpcMethodName, - networkBehaviour.__getTypeName(), - reader.Length); - } +#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) + networkBehaviour.TrackRpcMetricsReceive(ref metadata, ref context, reader.Length); #endif return true; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs index d9e1d2cbdf..a1e12a5310 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs @@ -52,21 +52,10 @@ protected void CheckLockBeforeDispose() private protected void SendMessageToClient(NetworkBehaviour behaviour, ulong clientId, ref RpcMessage message, NetworkDelivery delivery) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE - var size = -#endif - behaviour.NetworkManager.MessageManager.SendMessage(ref message, delivery, clientId); - -#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE - if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) - { - behaviour.NetworkManager.NetworkMetrics.TrackRpcSent( - clientId, - behaviour.NetworkObject, - rpcMethodName, - behaviour.__getTypeName(), - size); - } + var size = behaviour.NetworkManager.MessageManager.SendMessage(ref message, delivery, clientId); +#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) + // Send to a specific client + behaviour.TrackRpcMetricsSend(clientId, ref message, size); #endif } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs index 4c6567c262..386af8816f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs @@ -46,16 +46,9 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, message.Handle(ref context); length = tempBuffer.Length; } -#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE - if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) - { - networkManager.NetworkMetrics.TrackRpcSent( - networkManager.LocalClientId, - behaviour.NetworkObject, - rpcMethodName, - behaviour.__getTypeName(), - length); - } +#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) + // Local invocation sends to self + behaviour.TrackRpcMetricsSend(m_NetworkManager.LocalClientId, ref message, length); #endif } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs index 5c58b4f488..8dd2e744eb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs @@ -23,25 +23,14 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, return; } var proxyMessage = new ProxyMessage { Delivery = delivery, TargetClientIds = TargetClientIds.AsArray(), WrappedMessage = message }; -#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE - var size = -#endif - behaviour.NetworkManager.MessageManager.SendMessage(ref proxyMessage, delivery, NetworkManager.ServerClientId); - -#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE - if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) + var size = behaviour.NetworkManager.MessageManager.SendMessage(ref proxyMessage, delivery, NetworkManager.ServerClientId); +#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) + foreach (var clientId in TargetClientIds) { - foreach (var clientId in TargetClientIds) - { - behaviour.NetworkManager.NetworkMetrics.TrackRpcSent( - clientId, - behaviour.NetworkObject, - rpcMethodName, - behaviour.__getTypeName(), - size); - } + behaviour.TrackRpcMetricsSend(clientId, ref message, size); } #endif + if (Ids.Contains(NetworkManager.ServerClientId)) { m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs index 87bfbfb1c4..1b40931a5b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs @@ -43,18 +43,9 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, using var tempBuffer = new FastBufferReader(message.WriteBuffer, Allocator.None); message.ReadBuffer = tempBuffer; message.Handle(ref context); - // If enabled, then add the RPC metrics for this -#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE - int length = tempBuffer.Length; - if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) - { - m_NetworkManager.NetworkMetrics.TrackRpcSent( - m_NetworkManager.LocalClientId, - behaviour.NetworkObject, - rpcMethodName, - behaviour.__getTypeName(), - length); - } +#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) + // Local invocation sends to self + behaviour.TrackRpcMetricsSend(m_NetworkManager.LocalClientId, ref message, tempBuffer.Length); #endif } else // Otherwise, send a proxied message to the owner of the object diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcDuringOnNetworkSpawn.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcDuringOnNetworkSpawn.cs new file mode 100644 index 0000000000..7efe6d1626 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcDuringOnNetworkSpawn.cs @@ -0,0 +1,100 @@ +using System.Collections; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(NetworkTopologyTypes.DistributedAuthority, true)] + [TestFixture(NetworkTopologyTypes.ClientServer, true)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority, false)] + [TestFixture(NetworkTopologyTypes.ClientServer, false)] + internal class RpcDuringOnNetworkSpawn : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + private bool m_EnableSceneManagement; + + public RpcDuringOnNetworkSpawn(NetworkTopologyTypes topologyType, bool enableSceneManagement) : base(topologyType) + { + m_EnableSceneManagement = enableSceneManagement; + } + + /// + /// Enables or disables scene management when the s + /// are created. + /// + protected override void OnServerAndClientsCreated() + { + foreach (var networkManager in m_NetworkManagers) + { + networkManager.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement; + } + base.OnServerAndClientsCreated(); + } + + protected override void OnPlayerPrefabGameObjectCreated() + { + var first = m_PlayerPrefab.AddComponent(); + var second = m_PlayerPrefab.AddComponent(); + first.SecondNetworkBehaviour = second; + base.OnPlayerPrefabGameObjectCreated(); + } + + /// + /// Validates that invoking an RPC on another NetworkBehvaiour from within + /// OnNetworkSpawn when scene management is disabled does not throw an exception + /// + [UnityTest] + public IEnumerator ValidateRPCInvocationDuringNetworkSpawn() + { + // Just validating the values sent, but in reality if this breaks then an exception would be thrown prior to reaching this point. + foreach (var networkManager in m_NetworkManagers) + { + var first = networkManager.LocalClient.PlayerObject.GetComponent(); + var second = networkManager.LocalClient.PlayerObject.GetComponent(); + + Assert.IsTrue(first.ValueSent == second.ValueReceived, $"[{networkManager.LocalClient.PlayerObject.name}] Value sent {first.ValueSent} does not equal the value received {second.ValueReceived}!"); + } + + yield return null; + } + + + #region Test Components + /// + /// Should be added before the . + /// This invokes the RPC on the + /// during . + /// + public class FirstNetworkBehaviour : NetworkBehaviour + { + public SecondNetworkBehaviour SecondNetworkBehaviour; + + public int ValueSent { get; private set; } + public override void OnNetworkSpawn() + { + // Just invoke on the local player to test for this issue + if (IsLocalPlayer) + { + ValueSent = Random.Range(0, 100); + SecondNetworkBehaviour.SomeRpc(ValueSent); + } + base.OnNetworkSpawn(); + } + } + + public class SecondNetworkBehaviour : NetworkBehaviour + { + public int ValueReceived { get; private set; } + + [Rpc(SendTo.Owner)] + public void SomeRpc(int valueReceived) + { + ValueReceived = valueReceived; + } + } + #endregion + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcDuringOnNetworkSpawn.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcDuringOnNetworkSpawn.cs.meta new file mode 100644 index 0000000000..fcd002110e --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcDuringOnNetworkSpawn.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0d296381391ec064b9815743cc161bc9 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs index db6be63980..1705ad7f7f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs @@ -1,3 +1,4 @@ +#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -513,3 +514,4 @@ private static string GetCaller([CallerMemberName] string caller = null) } } } +#endif