diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index a6c19709f3..4c5e9ae6af 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -7,8 +7,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). ## [Unreleased] + ### Added +- Added methods NetworkManager.SetPeerMTU and NetworkManager.GetPeerMTU to be able to set MTU sizes per-peer (#2676) + ### Fixed - Fixed issue where `SpawnWithObservers` was not being honored when `NetworkConfig.EnableSceneManagement` was disabled. (#2682) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 7bcedfdcbf..04e6dd4c0b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -581,7 +581,12 @@ private void OnEnable() /// /// Sets the maximum size of a single non-fragmented message (or message batch) passed through the transport. - /// This should represent the transport's MTU size, minus any transport-level overhead. + /// This should represent the transport's default MTU size, minus any transport-level overhead. + /// This value will be used for any remote endpoints that haven't had per-endpoint MTUs set. + /// This value is also used as the size of the temporary buffer used when serializing + /// a single message (to avoid serializing multiple times when sending to multiple endpoints), + /// and thus should be large enough to ensure it can hold each message type. + /// This value defaults to 1296. /// /// public int MaximumTransmissionUnitSize @@ -590,6 +595,34 @@ public int MaximumTransmissionUnitSize get => MessageManager.NonFragmentedMessageMaxSize; } + /// + /// Set the maximum transmission unit for a specific peer. + /// This determines the maximum size of a message batch that can be sent to that client. + /// If not set for any given client, will be used instead. + /// + /// + /// + public void SetPeerMTU(ulong clientId, int size) + { + MessageManager.PeerMTUSizes[clientId] = size; + } + + /// + /// Queries the current MTU size for a client. + /// If no MTU has been set for that client, will return + /// + /// + /// + public int GetPeerMTU(ulong clientId) + { + if (MessageManager.PeerMTUSizes.TryGetValue(clientId, out var ret)) + { + return ret; + } + + return MessageManager.NonFragmentedMessageMaxSize; + } + /// /// Sets the maximum size of a message (or message batch) passed through the transport with the ReliableFragmented delivery. /// Warning: setting this value too low may result in the SDK becoming non-functional with projects that have a large number of NetworkBehaviours or NetworkVariables, as the SDK relies on the transport's ability to fragment some messages when they grow beyond the MTU size. diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs index 6caf7310bd..e66b54e4a4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs @@ -99,6 +99,8 @@ internal uint GetMessageType(Type t) public int NonFragmentedMessageMaxSize = DefaultNonFragmentedMessageMaxSize; public int FragmentedMessageMaxSize = int.MaxValue; + public Dictionary PeerMTUSizes = new Dictionary(); + internal struct MessageWithHandler { public Type MessageType; @@ -497,6 +499,7 @@ private void CleanupDisconnectedClient(ulong clientId) m_SendQueues.Remove(clientId); m_PerClientMessageVersions.Remove(clientId); + PeerMTUSizes.Remove(clientId); } internal void CleanupDisconnectedClients() @@ -678,6 +681,21 @@ internal unsafe int SendPreSerializedMessage(in FastBufferWriter t continue; } + var startSize = NonFragmentedMessageMaxSize; + if (delivery != NetworkDelivery.ReliableFragmentedSequenced) + { + if (PeerMTUSizes.TryGetValue(clientId, out var clientMaxSize)) + { + maxSize = clientMaxSize; + } + startSize = maxSize; + if (tmpSerializer.Position >= maxSize) + { + Debug.LogError($"MTU size for {clientId} is too small to contain a message of type {typeof(TMessageType).FullName}"); + continue; + } + } + for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) { m_Hooks[hookIdx].OnBeforeSendMessage(clientId, ref message, delivery); @@ -686,7 +704,7 @@ internal unsafe int SendPreSerializedMessage(in FastBufferWriter t var sendQueueItem = m_SendQueues[clientId]; if (sendQueueItem.Length == 0) { - sendQueueItem.Add(new SendQueueItem(delivery, NonFragmentedMessageMaxSize, Allocator.TempJob, maxSize)); + sendQueueItem.Add(new SendQueueItem(delivery, startSize, Allocator.TempJob, maxSize)); sendQueueItem.ElementAt(0).Writer.Seek(sizeof(NetworkBatchHeader)); } else @@ -694,7 +712,7 @@ internal unsafe int SendPreSerializedMessage(in FastBufferWriter t ref var lastQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1); if (lastQueueItem.NetworkDelivery != delivery || lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position < tmpSerializer.Length + headerSerializer.Length) { - sendQueueItem.Add(new SendQueueItem(delivery, NonFragmentedMessageMaxSize, Allocator.TempJob, maxSize)); + sendQueueItem.Add(new SendQueueItem(delivery, startSize, Allocator.TempJob, maxSize)); sendQueueItem.ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(NetworkBatchHeader)); } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs index a7492bc974..81cc7e7bb2 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageSendingTests.cs @@ -181,6 +181,64 @@ public void WhenExceedingBatchSize_NewBatchesAreCreated([Values(500, 1000, 1300, Assert.AreEqual(2, m_MessageSender.MessageQueue.Count); } + [Test] + public void WhenExceedingPerClientBatchSizeLessThanDefault_NewBatchesAreCreated([Values(500, 1000, 1300, 2000)] int maxMessageSize) + { + var message = GetMessage(); + m_MessageManager.NonFragmentedMessageMaxSize = maxMessageSize * 5; + var clients = new ulong[] { 0, 1, 2 }; + m_MessageManager.ClientConnected(1); + m_MessageManager.ClientConnected(2); + m_MessageManager.SetVersion(1, XXHash.Hash32(typeof(TestMessage).FullName), 0); + m_MessageManager.SetVersion(2, XXHash.Hash32(typeof(TestMessage).FullName), 0); + + for (var i = 0; i < clients.Length; ++i) + { + m_MessageManager.PeerMTUSizes[clients[i]] = maxMessageSize * (i + 1); + } + + var size = UnsafeUtility.SizeOf() + 2; // MessageHeader packed with this message will be 2 bytes + for (var i = 0; i < clients.Length; ++i) + { + for (var j = 0; j < ((m_MessageManager.PeerMTUSizes[clients[i]] - UnsafeUtility.SizeOf()) / size) + 1; ++j) + { + m_MessageManager.SendMessage(ref message, NetworkDelivery.Reliable, clients[i]); + } + } + + m_MessageManager.ProcessSendQueues(); + Assert.AreEqual(2 * clients.Length, m_MessageSender.MessageQueue.Count); + } + + [Test] + public void WhenExceedingPerClientBatchSizeGreaterThanDefault_OnlyOneNewBatcheIsCreated([Values(500, 1000, 1300, 2000)] int maxMessageSize) + { + var message = GetMessage(); + m_MessageManager.NonFragmentedMessageMaxSize = 128; + var clients = new ulong[] { 0, 1, 2 }; + m_MessageManager.ClientConnected(1); + m_MessageManager.ClientConnected(2); + m_MessageManager.SetVersion(1, XXHash.Hash32(typeof(TestMessage).FullName), 0); + m_MessageManager.SetVersion(2, XXHash.Hash32(typeof(TestMessage).FullName), 0); + + for (var i = 0; i < clients.Length; ++i) + { + m_MessageManager.PeerMTUSizes[clients[i]] = maxMessageSize * (i + 1); + } + + var size = UnsafeUtility.SizeOf() + 2; // MessageHeader packed with this message will be 2 bytes + for (var i = 0; i < clients.Length; ++i) + { + for (var j = 0; j < ((m_MessageManager.PeerMTUSizes[clients[i]] - UnsafeUtility.SizeOf()) / size) + 1; ++j) + { + m_MessageManager.SendMessage(ref message, NetworkDelivery.Reliable, clients[i]); + } + } + + m_MessageManager.ProcessSendQueues(); + Assert.AreEqual(2 * clients.Length, m_MessageSender.MessageQueue.Count); + } + [Test] public void WhenExceedingMTUSizeWithFragmentedDelivery_NewBatchesAreNotCreated([Values(500, 1000, 1300, 2000)] int maxMessageSize) {