From b89ccd31c14da846aebe24c4210c2fb7af602267 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 12 Nov 2025 15:54:35 -0600 Subject: [PATCH 01/15] fix Improving our defines that determine if metrics should be gathered. 1st pass attempt at unifying this process. The primary change is that if the __rpc_name_table is not built (yet) when an RPC is invoked that it will attempt to build it for that specific NetworkBehaviour prior to attempting to log RPC metrics. --- .../Runtime/Core/NetworkBehaviour.cs | 107 +++++++++++++++--- .../Runtime/Core/NetworkManager.cs | 2 +- .../Runtime/Messaging/Messages/RpcMessages.cs | 13 +-- .../Messaging/RpcTargets/BaseRpcTarget.cs | 18 +-- .../RpcTargets/LocalSendRpcTarget.cs | 12 +- .../RpcTargets/ProxyRpcTargetGroup.cs | 20 +--- .../Messaging/RpcTargets/ServerRpcTarget.cs | 15 +-- 7 files changed, 105 insertions(+), 82 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index a76066c237..d0d6f093be 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -40,7 +40,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 +142,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 +268,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) @@ -962,13 +959,91 @@ 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) + private bool ValidateRpcMessageMetrics(Type type) + { + if (m_NetworkManager == null) + { + Debug.LogError($"[{type.Name}] Attempting to invoking an RPC before {nameof(NetworkManager)} has been initialized within this {nameof(NetworkBehaviour)}!"); + // error and exit + return false; + } + + if (!__rpc_name_table.ContainsKey(type)) + { + __initializeRpcs(); + if (!__rpc_name_table.ContainsKey(type)) + { + Debug.LogError($"[{nameof(TrackRpcMetricsSend)}] Rpc table does not contain an entry for {type.Name}! Failed to initialize RPCs for {type.Name}."); + return false; + } + } + return true; + } + + 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); + } + } + + internal void TrackRpcMetricsSend(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); + } + } + + 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. diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 50a6304f52..7ae3757d9f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -49,7 +49,7 @@ 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 +#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 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..e937a86c90 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs @@ -52,21 +52,9 @@ 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) + behaviour.TrackRpcMetricsSend(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..efc61badb8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs @@ -46,16 +46,8 @@ 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) + behaviour.TrackRpcMetricsSend(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..83a1825f53 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs @@ -23,25 +23,11 @@ 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 = + 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) + behaviour.TrackRpcMetricsSend(ref message, 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)) - { - foreach (var clientId in TargetClientIds) - { - behaviour.NetworkManager.NetworkMetrics.TrackRpcSent( - clientId, - behaviour.NetworkObject, - rpcMethodName, - behaviour.__getTypeName(), - 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..090bd46f97 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs @@ -43,19 +43,10 @@ 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) + behaviour.TrackRpcMetricsSend(ref message, tempBuffer.Length); #endif + } else // Otherwise, send a proxied message to the owner of the object { From 43e079593acea7bbb02ce86c68affb989923c96b Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 12 Nov 2025 16:28:49 -0600 Subject: [PATCH 02/15] fix Missed one set of defines check. --- com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index d0d6f093be..a387dae387 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -1086,7 +1086,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(); From ec0910616eff6bdb0e2a49b1e2377d79b0f89961 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 12 Nov 2025 17:15:04 -0600 Subject: [PATCH 03/15] fix Removing reference to NetworkBehaviour.__rpc_name_table as it is not used any longer within the codegen process. --- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 2 -- 1 file changed, 2 deletions(-) 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); From d18cf066c4bd50dce4971cece112e87d71f7a4f6 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 12 Nov 2025 17:39:03 -0600 Subject: [PATCH 04/15] fix Some additional areas in codegen that requires the extended define wrap. Wrapping the RPC invocation tests (we will need to find a better way to get the name for this test). --- .../Editor/CodeGen/RuntimeAccessModifiersILPP.cs | 6 ++++-- .../Editor/CodeGen/Unity.Netcode.Editor.CodeGen.asmdef | 5 +++++ .../Tests/Runtime/Rpc/RpcInvocationTests.cs | 2 ++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs index bdcc74e498..a0010425ae 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs @@ -106,11 +106,12 @@ private void ProcessNetworkManager(TypeDefinition typeDefinition, string[] assem { fieldDefinition.IsPublic = true; } - +#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) if (fieldDefinition.Name == nameof(NetworkManager.__rpc_name_table)) { fieldDefinition.IsPublic = true; } +#endif } foreach (var nestedTypeDefinition in typeDefinition.NestedTypes) @@ -151,11 +152,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/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 From f9f721ac2d9cea970f26f5c8c829e38b5ab42f5f Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 12 Nov 2025 18:13:21 -0600 Subject: [PATCH 05/15] refactor Making sure we still have the NetworkManager.__rpc_name_table as that has become part of the public API. --- .../Editor/CodeGen/RuntimeAccessModifiersILPP.cs | 3 +-- com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs index a0010425ae..57295be52e 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs @@ -106,12 +106,11 @@ private void ProcessNetworkManager(TypeDefinition typeDefinition, string[] assem { fieldDefinition.IsPublic = true; } -#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) + if (fieldDefinition.Name == nameof(NetworkManager.__rpc_name_table)) { fieldDefinition.IsPublic = true; } -#endif } foreach (var nestedTypeDefinition in typeDefinition.NestedTypes) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 7ae3757d9f..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 MULTIPLAYER_TOOLS && (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 From 593c2f7bfda5cdeb55b9624f4aab8fbcf20e9592 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 13 Nov 2025 09:27:35 -0600 Subject: [PATCH 06/15] fix This fixes the issue with invoking RPCs on other child NetworkBehaviours during OnNetworkSpawn. --- .../Runtime/Core/NetworkBehaviour.cs | 17 +++++++++++------ .../Runtime/Core/NetworkObject.cs | 13 +++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index a387dae387..4ee0164962 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -779,13 +779,22 @@ internal void NetworkPreSpawn(ref NetworkManager networkManager, NetworkObject n } } + /// + /// Handles the initialization of all child s. + /// internal void InternalOnNetworkSpawn() { IsSpawned = true; // Initialize the NetworkVariables so they are accessible in OnNetworkSpawn; InitializeVariables(); UpdateNetworkProperties(); + } + /// + /// Handles invoking . + /// + internal void NetworkSpawn() + { try { OnNetworkSpawn(); @@ -979,12 +988,8 @@ private bool ValidateRpcMessageMetrics(Type type) if (!__rpc_name_table.ContainsKey(type)) { - __initializeRpcs(); - if (!__rpc_name_table.ContainsKey(type)) - { - Debug.LogError($"[{nameof(TrackRpcMetricsSend)}] Rpc table does not contain an entry for {type.Name}! Failed to initialize RPCs for {type.Name}."); - return false; - } + Debug.LogError($"[{nameof(TrackRpcMetricsSend)}] Rpc table does not contain an entry for {type.Name}! Failed to initialize RPCs for {type.Name}."); + return false; } return true; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index e29097426d..37353402bb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2556,6 +2556,8 @@ internal void InvokeBehaviourNetworkSpawn() { NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); + // Always invoke all internal network spawn methods on each child NetworkBehaviour + // ** before ** invoking OnNetworkSpawn. foreach (var childBehaviour in ChildNetworkBehaviours) { if (!childBehaviour.gameObject.activeInHierarchy) @@ -2565,6 +2567,17 @@ internal void InvokeBehaviourNetworkSpawn() } childBehaviour.InternalOnNetworkSpawn(); } + + // 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() From 4e5dfcc4d544283450f054b26fb56a9b21028a44 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 13 Nov 2025 09:57:41 -0600 Subject: [PATCH 07/15] test Adding a test to validate this specific issue is fixed. --- .../Runtime/Rpc/RpcDuringOnNetworkSpawn.cs | 100 ++++++++++++++++++ .../Rpc/RpcDuringOnNetworkSpawn.cs.meta | 2 + 2 files changed, 102 insertions(+) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcDuringOnNetworkSpawn.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcDuringOnNetworkSpawn.cs.meta 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..e0c8c69ae6 --- /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 From 07ca6e2070803fc0277212d89ae01d6f5909c9ec Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 13 Nov 2025 10:02:01 -0600 Subject: [PATCH 08/15] style Removing trailing whitespace. --- .../Tests/Runtime/Rpc/RpcDuringOnNetworkSpawn.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcDuringOnNetworkSpawn.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcDuringOnNetworkSpawn.cs index e0c8c69ae6..7efe6d1626 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcDuringOnNetworkSpawn.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcDuringOnNetworkSpawn.cs @@ -22,7 +22,7 @@ public RpcDuringOnNetworkSpawn(NetworkTopologyTypes topologyType, bool enableSce } /// - /// Enables or disables scene management when the s + /// Enables or disables scene management when the s /// are created. /// protected override void OnServerAndClientsCreated() From dda40db5563c82cd49a1a14114f135d94ae3af01 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 13 Nov 2025 11:32:49 -0600 Subject: [PATCH 09/15] update Marking newly added methods for aggressive in-lining. --- .../Runtime/Core/NetworkBehaviour.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 4ee0164962..046874b96e 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; @@ -977,23 +978,25 @@ internal void __registerRpc(uint hash, RpcReceiveHandler handler, string rpcMeth } #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($"[{type.Name}] Attempting to invoking an RPC before {nameof(NetworkManager)} has been initialized within this {nameof(NetworkBehaviour)}!"); + Debug.LogError($"[{nameof(TrackRpcMetricsSend)}][{type.Name}] {nameof(NetworkBehaviour)} is attempting to invoking an RPC before {nameof(NetworkManager)} has been initialized!"); // error and exit return false; } if (!__rpc_name_table.ContainsKey(type)) { - Debug.LogError($"[{nameof(TrackRpcMetricsSend)}] Rpc table does not contain an entry for {type.Name}! Failed to initialize RPCs for {type.Name}."); + Debug.LogError($"[{nameof(TrackRpcMetricsSend)}][{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(); @@ -1012,6 +1015,7 @@ internal void TrackRpcMetricsSend(ref ServerRpcMessage message, uint rpcMethodId } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void TrackRpcMetricsSend(ref RpcMessage message, int length) { var type = GetType(); @@ -1030,6 +1034,7 @@ internal void TrackRpcMetricsSend(ref RpcMessage message, int length) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void TrackRpcMetricsReceive(ref RpcMetadata metadata, ref NetworkContext context, int length) { var type = GetType(); From 202e005b55bb962a97908d5a60d8fe4a33d462f6 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 13 Nov 2025 11:33:00 -0600 Subject: [PATCH 10/15] update Adding change log entry. --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 2eb3776c3b..be236a8b27 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -24,6 +24,8 @@ 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) + ### Security From bee700f351cef46be28cbb4c97aff2e6a8466371 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 13 Nov 2025 11:44:26 -0600 Subject: [PATCH 11/15] style Fixing a comment. Adding additional comments. --- .../Runtime/Core/NetworkBehaviour.cs | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 046874b96e..56e06ac908 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -753,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; @@ -781,13 +786,19 @@ internal void NetworkPreSpawn(ref NetworkManager networkManager, NetworkObject n } /// - /// Handles the initialization of all child s. + /// 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(); } @@ -817,6 +828,9 @@ internal void NetworkSpawn() } } + /// + /// Handles invoking . + /// internal void NetworkPostSpawn() { try @@ -830,6 +844,9 @@ internal void NetworkPostSpawn() } } + /// + /// Handles invoking . + /// internal void NetworkSessionSynchronized() { try @@ -843,6 +860,9 @@ internal void NetworkSessionSynchronized() } } + /// + /// Handles invoking . + /// internal void InSceneNetworkObjectsSpawned() { try @@ -855,6 +875,9 @@ internal void InSceneNetworkObjectsSpawned() } } + /// + /// Handles invoking . + /// internal void InternalOnNetworkPreDespawn() { try @@ -867,6 +890,9 @@ internal void InternalOnNetworkPreDespawn() } } + /// + /// Handles invoking . + /// internal void InternalOnNetworkDespawn() { IsSpawned = false; From fd5ba9b3cea12b8a7f3216729db33a93489d0ced Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 13 Nov 2025 11:47:48 -0600 Subject: [PATCH 12/15] style Making sure the correct ValidateRpcMessageMetrics name is used when logging an error. --- .../Runtime/Core/NetworkBehaviour.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 56e06ac908..8ae66236f0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -1009,14 +1009,13 @@ private bool ValidateRpcMessageMetrics(Type type) { if (m_NetworkManager == null) { - Debug.LogError($"[{nameof(TrackRpcMetricsSend)}][{type.Name}] {nameof(NetworkBehaviour)} is attempting to invoking an RPC before {nameof(NetworkManager)} has been initialized!"); - // error and exit + 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(TrackRpcMetricsSend)}][{type.Name}][{nameof(__rpc_name_table)}] RPC table initialization failure: Table does not contain an entry for {type.Name}!"); + 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; From ce720f2b9f8a46ee6200b1b4d10936ed5318ad95 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 13 Nov 2025 12:03:37 -0600 Subject: [PATCH 13/15] style Updated and added some comments to the region of code that contains the primary fix. --- .../Runtime/Core/NetworkObject.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 37353402bb..953781d008 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2556,8 +2556,12 @@ internal void InvokeBehaviourNetworkSpawn() { NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); - // Always invoke all internal network spawn methods on each child NetworkBehaviour + // 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) @@ -2568,7 +2572,7 @@ internal void InvokeBehaviourNetworkSpawn() childBehaviour.InternalOnNetworkSpawn(); } - // Invoke OnNetworkSpawn on each child NetworkBehaviour + // After initialization, we can then invoke OnNetworkSpawn on each child NetworkBehaviour. foreach (var childBehaviour in ChildNetworkBehaviours) { if (!childBehaviour.gameObject.activeInHierarchy) @@ -2591,7 +2595,6 @@ internal void InvokeBehaviourNetworkPostSpawn() } } - internal void InternalNetworkSessionSynchronized() { for (int i = 0; i < ChildNetworkBehaviours.Count; i++) From 3189abf0d9b926143054fa7858b059846f9577ae Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 14 Nov 2025 09:41:54 -0600 Subject: [PATCH 14/15] fix Adding target client that is being sent to. --- .../Runtime/Core/NetworkBehaviour.cs | 3 +-- .../Runtime/Messaging/RpcTargets/BaseRpcTarget.cs | 3 ++- .../Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs | 3 ++- .../Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs | 5 ++++- .../Runtime/Messaging/RpcTargets/ServerRpcTarget.cs | 4 ++-- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 8ae66236f0..5175eec83d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -1040,8 +1040,7 @@ internal void TrackRpcMetricsSend(ref ServerRpcMessage message, uint rpcMethodId } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void TrackRpcMetricsSend(ref RpcMessage message, int length) + internal void TrackRpcMetricsSend(ulong clientId, ref RpcMessage message, int length) { var type = GetType(); if (!ValidateRpcMessageMetrics(type)) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs index e937a86c90..a1e12a5310 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs @@ -54,7 +54,8 @@ private protected void SendMessageToClient(NetworkBehaviour behaviour, ulong cli { 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) - behaviour.TrackRpcMetricsSend(ref message, size); + // 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 efc61badb8..386af8816f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs @@ -47,7 +47,8 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, length = tempBuffer.Length; } #if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) - behaviour.TrackRpcMetricsSend(ref message, length); + // 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 83a1825f53..8dd2e744eb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs @@ -25,7 +25,10 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, var proxyMessage = new ProxyMessage { Delivery = delivery, TargetClientIds = TargetClientIds.AsArray(), WrappedMessage = message }; 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) - behaviour.TrackRpcMetricsSend(ref message, size); + foreach (var clientId in TargetClientIds) + { + behaviour.TrackRpcMetricsSend(clientId, ref message, size); + } #endif if (Ids.Contains(NetworkManager.ServerClientId)) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs index 090bd46f97..1b40931a5b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs @@ -44,9 +44,9 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, message.ReadBuffer = tempBuffer; message.Handle(ref context); #if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) - behaviour.TrackRpcMetricsSend(ref message, tempBuffer.Length); + // 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 { From 0b0cba2264583d6817f1ba1945ae352cb84933ea Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 14 Nov 2025 09:43:00 -0600 Subject: [PATCH 15/15] update Adding AggressiveInlining to updated method. --- com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 5175eec83d..e807af3fb7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -1040,6 +1040,7 @@ internal void TrackRpcMetricsSend(ref ServerRpcMessage message, uint rpcMethodId } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void TrackRpcMetricsSend(ulong clientId, ref RpcMessage message, int length) { var type = GetType();