Skip to content

Commit 79b465d

Browse files
fix: Don't coalesce small unreliable messages into packets larger than the MTU (#2784)
* Modify test to use mixed small and large packets * Add new test for BatchedSendQueue * Limit unreliable coalescing of small messages to MTU
1 parent 7ab9947 commit 79b465d

File tree

4 files changed

+94
-27
lines changed

4 files changed

+94
-27
lines changed

com.unity.netcode.gameobjects/Runtime/Transports/UTP/BatchedSendQueue.cs

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -198,43 +198,69 @@ public bool PushMessage(ArraySegment<byte> message)
198198
/// could lead to a corrupted queue.
199199
/// </remarks>
200200
/// <param name="writer">The <see cref="DataStreamWriter"/> to write to.</param>
201+
/// <param name="softMaxBytes">
202+
/// Maximum number of bytes to copy (0 means writer capacity). This is a soft limit only.
203+
/// If a message is larger than that but fits in the writer, it will be written. In effect,
204+
/// this parameter is the maximum size that small messages can be coalesced together.
205+
/// </param>
201206
/// <returns>How many bytes were written to the writer.</returns>
202-
public int FillWriterWithMessages(ref DataStreamWriter writer)
207+
public int FillWriterWithMessages(ref DataStreamWriter writer, int softMaxBytes = 0)
203208
{
204209
if (!IsCreated || Length == 0)
205210
{
206211
return 0;
207212
}
208213

214+
softMaxBytes = softMaxBytes == 0 ? writer.Capacity : Math.Min(softMaxBytes, writer.Capacity);
215+
209216
unsafe
210217
{
211218
var reader = new DataStreamReader(m_Data.AsArray());
212-
213-
var writerAvailable = writer.Capacity;
214219
var readerOffset = HeadIndex;
215220

216-
while (readerOffset < TailIndex)
221+
reader.SeekSet(readerOffset);
222+
var messageLength = reader.ReadInt();
223+
var bytesToWrite = messageLength + sizeof(int);
224+
225+
// Our behavior here depends on the size of the first message in the queue. If it's
226+
// larger than the soft limit, then add only that message to the writer (we want
227+
// large payloads to be fragmented on their own). Otherwise coalesce all small
228+
// messages until we hit the soft limit (which presumably means they won't be
229+
// fragmented, which is the desired behavior for smaller messages).
230+
231+
if (bytesToWrite > softMaxBytes && bytesToWrite <= writer.Capacity)
217232
{
218-
reader.SeekSet(readerOffset);
219-
var messageLength = reader.ReadInt();
233+
writer.WriteInt(messageLength);
234+
WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + reader.GetBytesRead(), messageLength);
220235

221-
if (writerAvailable < sizeof(int) + messageLength)
236+
return bytesToWrite;
237+
}
238+
else
239+
{
240+
var bytesWritten = 0;
241+
242+
while (readerOffset < TailIndex)
222243
{
223-
break;
244+
reader.SeekSet(readerOffset);
245+
messageLength = reader.ReadInt();
246+
bytesToWrite = messageLength + sizeof(int);
247+
248+
if (bytesWritten + bytesToWrite <= softMaxBytes)
249+
{
250+
writer.WriteInt(messageLength);
251+
WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + reader.GetBytesRead(), messageLength);
252+
253+
readerOffset += bytesToWrite;
254+
bytesWritten += bytesToWrite;
255+
}
256+
else
257+
{
258+
break;
259+
}
224260
}
225-
else
226-
{
227-
writer.WriteInt(messageLength);
228-
229-
var messageOffset = reader.GetBytesRead();
230-
WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength);
231261

232-
writerAvailable -= sizeof(int) + messageLength;
233-
readerOffset += sizeof(int) + messageLength;
234-
}
262+
return bytesWritten;
235263
}
236-
237-
return writer.Capacity - writerAvailable;
238264
}
239265
}
240266

com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,7 @@ public void Execute()
753753
// in the stream (the send queue does that automatically) we are sure they'll be
754754
// reassembled properly at the other end. This allows us to lift the limit of ~44KB
755755
// on reliable payloads (because of the reliable window size).
756-
var written = pipeline == ReliablePipeline ? Queue.FillWriterWithBytes(ref writer, MTU) : Queue.FillWriterWithMessages(ref writer);
756+
var written = pipeline == ReliablePipeline ? Queue.FillWriterWithBytes(ref writer, MTU) : Queue.FillWriterWithMessages(ref writer, MTU);
757757

758758
result = Driver.EndSend(writer);
759759
if (result == written)
@@ -1238,7 +1238,7 @@ public override void Initialize(NetworkManager networkManager = null)
12381238
// We also increase the maximum resend timeout since the default one in UTP is very
12391239
// aggressive (optimized for latency and low bandwidth). With NGO, it's too low and
12401240
// we sometimes notice a lot of useless resends, especially if using Relay. (We can
1241-
// only do this with UTP 2.0 because 1.X doesn't support that parameter.)
1241+
// only do this with UTP 2.0 because 1.X doesn't support that parameter.)
12421242
m_NetworkSettings.WithReliableStageParameters(
12431243
windowSize: 64
12441244
#if UTP_TRANSPORT_2_0_ABOVE

com.unity.netcode.gameobjects/Tests/Editor/Transports/BatchedSendQueueTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,35 @@ public void BatchedSendQueue_FillWriterWithMessages_PartialPushedMessages()
237237
AssertIsTestMessage(data);
238238
}
239239

240+
[Test]
241+
public void BatchedSendQueue_FillWriterWithMessages_StopOnSoftMaxBytes()
242+
{
243+
var smallMessage = new ArraySegment<byte>(new byte[10]);
244+
var largeMessage = new ArraySegment<byte>(new byte[3000]);
245+
246+
var smallMessageSize = smallMessage.Count + BatchedSendQueue.PerMessageOverhead;
247+
var largeMessageSize = largeMessage.Count + BatchedSendQueue.PerMessageOverhead;
248+
249+
using var q = new BatchedSendQueue(k_TestQueueCapacity);
250+
using var data = new NativeArray<byte>(largeMessageSize, Allocator.Temp);
251+
252+
q.PushMessage(smallMessage);
253+
q.PushMessage(largeMessage);
254+
q.PushMessage(smallMessage);
255+
256+
var writer = new DataStreamWriter(data);
257+
Assert.AreEqual(smallMessageSize, q.FillWriterWithMessages(ref writer, 1000));
258+
q.Consume(smallMessageSize);
259+
260+
writer = new DataStreamWriter(data);
261+
Assert.AreEqual(largeMessageSize, q.FillWriterWithMessages(ref writer, 1000));
262+
q.Consume(largeMessageSize);
263+
264+
writer = new DataStreamWriter(data);
265+
Assert.AreEqual(smallMessageSize, q.FillWriterWithMessages(ref writer, 1000));
266+
q.Consume(smallMessageSize);
267+
}
268+
240269
[Test]
241270
public void BatchedSendQueue_FillWriterWithBytes_NoopIfNoData()
242271
{

com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportTests.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,19 +192,31 @@ public IEnumerator MultipleSendsSingleFrame(
192192

193193
yield return WaitForNetworkEvent(NetworkEvent.Connect, m_Client1Events);
194194

195-
var data1 = new ArraySegment<byte>(new byte[] { 11 });
196-
m_Client1.Send(m_Client1.ServerClientId, data1, delivery);
195+
var data1 = new byte[10];
196+
data1[0] = 11;
197+
m_Client1.Send(m_Client1.ServerClientId, new ArraySegment<byte>(data1), delivery);
197198

198-
var data2 = new ArraySegment<byte>(new byte[] { 22 });
199-
m_Client1.Send(m_Client1.ServerClientId, data2, delivery);
199+
var data2 = new byte[3000];
200+
data2[0] = 22;
201+
m_Client1.Send(m_Client1.ServerClientId, new ArraySegment<byte>(data2), delivery);
202+
203+
var data3 = new byte[10];
204+
data3[0] = 33;
205+
m_Client1.Send(m_Client1.ServerClientId, new ArraySegment<byte>(data3), delivery);
200206

201207
yield return WaitForNetworkEvent(NetworkEvent.Data, m_ServerEvents);
202208

203-
Assert.AreEqual(3, m_ServerEvents.Count);
204-
Assert.AreEqual(NetworkEvent.Data, m_ServerEvents[2].Type);
209+
Assert.AreEqual(4, m_ServerEvents.Count);
210+
Assert.AreEqual(NetworkEvent.Data, m_ServerEvents[3].Type);
205211

206212
Assert.AreEqual(11, m_ServerEvents[1].Data.First());
213+
Assert.AreEqual(10, m_ServerEvents[1].Data.Count);
214+
207215
Assert.AreEqual(22, m_ServerEvents[2].Data.First());
216+
Assert.AreEqual(3000, m_ServerEvents[2].Data.Count);
217+
218+
Assert.AreEqual(33, m_ServerEvents[3].Data.First());
219+
Assert.AreEqual(10, m_ServerEvents[3].Data.Count);
208220

209221
yield return null;
210222
}

0 commit comments

Comments
 (0)