diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index dc1832df8b..1af4e45b31 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1243,6 +1243,7 @@ internal void HandleIncomingData(ulong clientId, NetworkChannel networkChannel, { var messageType = (MessageQueueContainer.MessageType)messageStream.ReadByte(); MessageHandler.MessageReceiveQueueItem(clientId, messageStream, receiveTime, messageType, networkChannel); + NetworkMetrics.TrackNetworkMessageReceived(clientId, MessageQueueContainer.GetMessageTypeName(messageType), data.Count); } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleIncomingData.End(); @@ -1253,6 +1254,7 @@ private void ReceiveCallback(NetworkBuffer messageBuffer, MessageQueueContainer. float receiveTime, NetworkChannel receiveChannel) { MessageHandler.MessageReceiveQueueItem(clientId, messageBuffer, receiveTime, messageType, receiveChannel); + NetworkMetrics.TrackNetworkMessageReceived(clientId, MessageQueueContainer.GetMessageTypeName(messageType), messageBuffer.Length); } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageBatcher.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageBatcher.cs index 7e27a36d56..978d4fcfa6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageBatcher.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageBatcher.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using System.Collections.Generic; namespace Unity.Netcode @@ -34,8 +33,7 @@ public void Shutdown() m_SendDict.Clear(); } - // Used to store targets, internally - private ulong[] m_TargetList = new ulong[0]; + // Used to mark longer lengths. Works because we can't have zero-sized messages private const byte k_LongLenMarker = 0; @@ -84,40 +82,18 @@ private int PopLength(in NetworkBuffer messageBuffer) return len1 + len2 * 256; } - /// - /// FillTargetList - /// Fills a list with the ClientId's an item is targeted to - /// - /// the FrameQueueItem we want targets for - /// the list to fill - private static void FillTargetList(in MessageFrameItem item, ref ulong[] networkIdList) - { - switch (item.MessageType) - { - // todo: revisit .resize() and .ToArry() usage, for performance - case MessageQueueContainer.MessageType.ServerRpc: - Array.Resize(ref networkIdList, 1); - networkIdList[0] = item.NetworkId; - break; - default: - // todo: consider the implications of default usage of queueItem.clientIds - case MessageQueueContainer.MessageType.ClientRpc: - // copy the list - networkIdList = item.ClientNetworkIds.ToArray(); - break; - } - } - /// /// QueueItem /// Add a FrameQueueItem to be sent /// queueItem /// the threshold in bytes - public void QueueItem(in MessageFrameItem item, int automaticSendThresholdBytes, SendCallbackType sendCallback) + public void QueueItem( + IReadOnlyCollection targetList, + in MessageFrameItem item, + int automaticSendThresholdBytes, + SendCallbackType sendCallback) { - FillTargetList(item, ref m_TargetList); - - foreach (ulong clientId in m_TargetList) + foreach (ulong clientId in targetList) { if (!m_SendDict.ContainsKey(clientId)) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueContainer.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueContainer.cs index fec17c426e..314ff409cc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueContainer.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueContainer.cs @@ -44,6 +44,29 @@ public enum MessageQueueProcessingTypes Receive, } + private static readonly IReadOnlyDictionary k_MessageTypeNames; + + static MessageQueueContainer() + { + var messageTypeNames = new Dictionary(); + foreach(var messageType in Enum.GetValues(typeof(MessageType))) + { + messageTypeNames.Add((int)messageType, messageType.ToString()); + } + + k_MessageTypeNames = messageTypeNames; + } + + public static string GetMessageTypeName(MessageType messageType) + { + if (!k_MessageTypeNames.TryGetValue((int)messageType, out var messageTypeName)) + { + messageTypeName = string.Empty; + } + + return messageTypeName; + } + // Inbound and Outbound QueueHistoryFrames private readonly Dictionary>> m_QueueHistory = new Dictionary>>(); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueProcessor.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueProcessor.cs index 93666fde25..f60c6a9321 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueProcessor.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageQueueProcessor.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Unity.Profiling; using UnityEngine; @@ -29,6 +30,7 @@ internal class MessageQueueProcessor private MessageQueueContainer m_MessageQueueContainer; private readonly NetworkManager m_NetworkManager; + private readonly List m_TargetIdBuffer = new List(); public void Shutdown() { @@ -217,6 +219,28 @@ internal void ProcessSendQueue(bool isListening) #endif } + /// + /// FillTargetList + /// Fills a list with the ClientId's an item is targeted to + /// + /// the MessageQueueItem we want targets for + /// the list to fill + private static void FillTargetList(in MessageFrameItem item, List targetList) + { + switch (item.MessageType) + { + case MessageQueueContainer.MessageType.ServerRpc: + targetList.Add(item.NetworkId); + break; + default: + // todo: consider the implications of default usage of queueItem.clientIds + case MessageQueueContainer.MessageType.ClientRpc: + // copy the list + targetList.AddRange(item.ClientNetworkIds); + break; + } + } + /// /// Sends all message queue items in the current outbound frame /// @@ -235,13 +259,21 @@ private void MessageQueueSendAndFlush(bool isListening) advanceFrameHistory = true; if (isListening) { + m_TargetIdBuffer.Clear(); + FillTargetList(currentQueueItem, m_TargetIdBuffer); + if (m_MessageQueueContainer.IsUsingBatching()) { - m_MessageBatcher.QueueItem(currentQueueItem, k_BatchThreshold, SendCallback); + m_MessageBatcher.QueueItem(m_TargetIdBuffer, currentQueueItem, k_BatchThreshold, SendCallback); } else { - SendFrameQueueItem(currentQueueItem); + SendFrameQueueItem(m_TargetIdBuffer, currentQueueItem); + } + + foreach (var target in m_TargetIdBuffer) + { + m_NetworkManager.NetworkMetrics.TrackNetworkMessageSent(target, MessageQueueContainer.GetMessageTypeName(currentQueueItem.MessageType), currentQueueItem.MessageData.Count); } } @@ -293,7 +325,7 @@ private void SendCallback(ulong clientId, MessageBatcher.SendStream sendStream) /// Sends the Message Queue Item to the specified destination /// /// Information on what to send - private void SendFrameQueueItem(MessageFrameItem item) + private void SendFrameQueueItem(IReadOnlyCollection targetIds, in MessageFrameItem item) { var channel = item.NetworkChannel; // If the length is greater than the fragmented threshold, switch to a fragmented channel. @@ -304,25 +336,11 @@ private void SendFrameQueueItem(MessageFrameItem item) { channel = NetworkChannel.Fragmented; } - switch (item.MessageType) - { - case MessageQueueContainer.MessageType.ServerRpc: - // TODO: Can we remove this special case for server RPCs? - { - m_MessageQueueContainer.NetworkManager.NetworkMetrics.TrackTransportBytesSent(item.MessageData.Count); - m_MessageQueueContainer.NetworkManager.NetworkConfig.NetworkTransport.Send(item.NetworkId, item.MessageData, channel); - break; - } - default: - { - foreach (ulong clientid in item.ClientNetworkIds) - { - m_MessageQueueContainer.NetworkManager.NetworkMetrics.TrackTransportBytesSent(item.MessageData.Count); - m_MessageQueueContainer.NetworkManager.NetworkConfig.NetworkTransport.Send(clientid, item.MessageData, channel); - } - break; - } + foreach (var clientId in targetIds) + { + m_MessageQueueContainer.NetworkManager.NetworkMetrics.TrackTransportBytesSent(item.MessageData.Count); + m_MessageQueueContainer.NetworkManager.NetworkConfig.NetworkTransport.Send(clientId, item.MessageData, channel); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Metrics/INetworkMetrics.cs b/com.unity.netcode.gameobjects/Runtime/Metrics/INetworkMetrics.cs index 14d7e3275e..05bf0f3098 100644 --- a/com.unity.netcode.gameobjects/Runtime/Metrics/INetworkMetrics.cs +++ b/com.unity.netcode.gameobjects/Runtime/Metrics/INetworkMetrics.cs @@ -8,6 +8,10 @@ internal interface INetworkMetrics void TrackTransportBytesReceived(long bytesCount); + void TrackNetworkMessageSent(ulong receivedClientId, string messageType, long bytesCount); + + void TrackNetworkMessageReceived(ulong senderClientId, string messageType, long bytesCount); + void TrackNetworkObject(NetworkObject networkObject); void TrackNamedMessageSent(ulong receiverClientId, string messageName, long bytesCount); diff --git a/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkMetrics.cs b/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkMetrics.cs index 1a816873fa..4d160fce18 100644 --- a/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkMetrics.cs +++ b/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkMetrics.cs @@ -17,6 +17,8 @@ internal class NetworkMetrics : INetworkMetrics ShouldResetOnDispatch = true, }; + readonly EventMetric m_NetworkMessageSentEvent = new EventMetric(NetworkMetricTypes.NetworkMessageSent.Id); + readonly EventMetric m_NetworkMessageReceivedEvent = new EventMetric(NetworkMetricTypes.NetworkMessageReceived.Id); readonly EventMetric m_NamedMessageSentEvent = new EventMetric(NetworkMetricTypes.NamedMessageSent.Id); readonly EventMetric m_NamedMessageReceivedEvent = new EventMetric(NetworkMetricTypes.NamedMessageReceived.Id); readonly EventMetric m_UnnamedMessageSentEvent = new EventMetric(NetworkMetricTypes.UnnamedMessageSent.Id); @@ -42,6 +44,7 @@ public NetworkMetrics() { Dispatcher = new MetricDispatcherBuilder() .WithCounters(m_TransportBytesSent, m_TransportBytesReceived) + .WithMetricEvents(m_NetworkMessageSentEvent, m_NetworkMessageReceivedEvent) .WithMetricEvents(m_NamedMessageSentEvent, m_NamedMessageReceivedEvent) .WithMetricEvents(m_UnnamedMessageSentEvent, m_UnnamedMessageReceivedEvent) .WithMetricEvents(m_NetworkVariableDeltaSentEvent, m_NetworkVariableDeltaReceivedEvent) @@ -76,6 +79,16 @@ public void TrackNetworkObject(NetworkObject networkObject) } } + public void TrackNetworkMessageSent(ulong receivedClientId, string messageType, long bytesCount) + { + m_NetworkMessageSentEvent.Mark(new NetworkMessageEvent(new ConnectionInfo(receivedClientId), messageType, bytesCount)); + } + + public void TrackNetworkMessageReceived(ulong senderClientId, string messageType, long bytesCount) + { + m_NetworkMessageReceivedEvent.Mark(new NetworkMessageEvent(new ConnectionInfo(senderClientId), messageType, bytesCount)); + } + public void TrackNamedMessageSent(ulong receiverClientId, string messageName, long bytesCount) { m_NamedMessageSentEvent.Mark(new NamedMessageEvent(new ConnectionInfo(receiverClientId), messageName, bytesCount)); diff --git a/com.unity.netcode.gameobjects/Runtime/Metrics/NullNetworkMetrics.cs b/com.unity.netcode.gameobjects/Runtime/Metrics/NullNetworkMetrics.cs index df27bb0857..9a7bd97b18 100644 --- a/com.unity.netcode.gameobjects/Runtime/Metrics/NullNetworkMetrics.cs +++ b/com.unity.netcode.gameobjects/Runtime/Metrics/NullNetworkMetrics.cs @@ -11,6 +11,14 @@ public void TrackTransportBytesSent(long bytesCount) public void TrackTransportBytesReceived(long bytesCount) { } + + public void TrackNetworkMessageSent(ulong receivedClientId, string messageType, long bytesCount) + { + } + + public void TrackNetworkMessageReceived(ulong senderClientId, string messageType, long bytesCount) + { + } public void TrackNetworkObject(NetworkObject networkObject) { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/MessageBatcherTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/MessageBatcherTests.cs index 53ffaa74f0..a88209bdad 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/MessageBatcherTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/MessageBatcherTests.cs @@ -26,7 +26,9 @@ public void SendWithThreshold() MessageType = i % 2 == 0 ? MessageQueueContainer.MessageType.ServerRpc : MessageQueueContainer.MessageType.ClientRpc, MessageData = new ArraySegment(randomData, 0, randomData.Length) }; - sendBatcher.QueueItem(queueItem, + sendBatcher.QueueItem( + queueItem.ClientNetworkIds, + queueItem, k_BatchThreshold, (networkId, sendStream) => { @@ -75,7 +77,9 @@ public void SendWithoutThreshold() MessageType = i % 2 == 0 ? MessageQueueContainer.MessageType.ServerRpc : MessageQueueContainer.MessageType.ClientRpc, MessageData = new ArraySegment(randomData, 0, randomData.Length) }; - sendBatcher.QueueItem(queueItem, + sendBatcher.QueueItem( + queueItem.ClientNetworkIds, + queueItem, k_BatchThreshold, (networkId, sendStream) => { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MessagingMetricsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MessagingMetricsTests.cs index f52488bb14..f721a6157c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MessagingMetricsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Metrics/MessagingMetricsTests.cs @@ -22,6 +22,73 @@ public class MessagingMetricsTests : DualClientMetricTestBase protected override int NbClients => 2; + [UnityTest] + public IEnumerator TrackNetworkMessageSentMetric() + { + var messageName = Guid.NewGuid().ToString(); + using var memoryStream = new MemoryStream(); + using var binaryWriter = new BinaryWriter(memoryStream); + binaryWriter.Write(messageName); + + var waitForMetricValues = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.NetworkMessageSent); + + Server.CustomMessagingManager.SendNamedMessage(messageName, FirstClient.LocalClientId, memoryStream); + + yield return waitForMetricValues.WaitForMetricsReceived(); + + var networkMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound(); + Assert.AreEqual(1, networkMessageSentMetricValues.Count); + + var networkMessageEvent = networkMessageSentMetricValues.First(); + Assert.AreEqual(MessageQueueContainer.GetMessageTypeName(MessageQueueContainer.MessageType.NamedMessage), networkMessageEvent.Name); + Assert.AreEqual(FirstClient.LocalClientId, networkMessageEvent.Connection.Id); + } + + [UnityTest] + public IEnumerator TrackNetworkMessageSentMetricToMultipleClients() + { + var messageName = Guid.NewGuid().ToString(); + using var memoryStream = new MemoryStream(); + using var binaryWriter = new BinaryWriter(memoryStream); + binaryWriter.Write(messageName); + + var waitForMetricValues = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.NetworkMessageSent); + + Server.CustomMessagingManager.SendNamedMessage(messageName, new List { FirstClient.LocalClientId, SecondClient.LocalClientId }, memoryStream); + + yield return waitForMetricValues.WaitForMetricsReceived(); + + var networkMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound(); + Assert.AreEqual(2, networkMessageSentMetricValues.Count(x => x.Name.Equals(MessageQueueContainer.GetMessageTypeName(MessageQueueContainer.MessageType.NamedMessage)))); + } + + [UnityTest] + public IEnumerator TrackNetworkMessageReceivedMetric() + { + var messageName = Guid.NewGuid().ToString(); + using var memoryStream = new MemoryStream(); + using var binaryWriter = new BinaryWriter(memoryStream); + binaryWriter.Write(messageName); + + LogAssert.Expect(LogType.Log, $"Received from {Server.LocalClientId}"); + FirstClient.CustomMessagingManager.RegisterNamedMessageHandler(messageName, (sender, payload) => + { + Debug.Log($"Received from {sender}"); + }); + + var waitForMetricValues = new WaitForMetricValues(FirstClientMetrics.Dispatcher, NetworkMetricTypes.NetworkMessageReceived); + + Server.CustomMessagingManager.SendNamedMessage(messageName, FirstClient.LocalClientId, memoryStream); + + yield return waitForMetricValues.WaitForMetricsReceived(); + + var networkMessageReceivedValues = waitForMetricValues.AssertMetricValuesHaveBeenFound(); + Assert.AreEqual(1, networkMessageReceivedValues.Count(x => x.Name.Equals(MessageQueueContainer.GetMessageTypeName(MessageQueueContainer.MessageType.NamedMessage)))); + + var namedMessageReceived = networkMessageReceivedValues.First(); + Assert.AreEqual(Server.LocalClientId, namedMessageReceived.Connection.Id); + } + [UnityTest] public IEnumerator TrackNamedMessageSentMetric() { diff --git a/testproject-tools-integration/Packages/manifest.json b/testproject-tools-integration/Packages/manifest.json index e3ff4cbc20..7d187e5d57 100644 --- a/testproject-tools-integration/Packages/manifest.json +++ b/testproject-tools-integration/Packages/manifest.json @@ -3,7 +3,7 @@ "dependencies": { "com.unity.ide.rider": "3.0.7", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", - "com.unity.multiplayer.tools": "0.0.1-preview.7", + "com.unity.multiplayer.tools": "0.0.1-preview.8", "com.unity.multiplayer.transport.utp": "file:../../com.unity.multiplayer.transport.utp", "com.unity.test-framework": "1.1.26", "com.unity.modules.ai": "1.0.0",