perf(net): frame recurring sends in place

Write normal game-message and ACK packet framing directly into bounded stack spans, hash fragments without materialization, and send the populated slice to the socket. Preserve exact wire bytes and ISAAC failure ordering with differential and zero-allocation tests.
This commit is contained in:
Erik 2026-07-25 06:02:52 +02:00
parent e928c5dd02
commit 41c1a59392
7 changed files with 485 additions and 31 deletions

View file

@ -149,4 +149,41 @@ public class GameMessageFragmentTests
Assert.Equal(original.Header.Count, reparsed.Value.Header.Count);
Assert.Equal(original.Payload, reparsed.Value.Payload);
}
[Fact]
public void WriteSingleFragment_MatchesOwnedBuildAndSerialize()
{
byte[] message = [1, 2, 3, 4, 5, 6];
MessageFragment owned =
GameMessageFragment.BuildSingleFragment(
fragmentSequence: 99,
queue: GameMessageGroup.ControlQueue,
gameMessageBytes: message);
byte[] expected = GameMessageFragment.Serialize(owned);
Span<byte> destination = stackalloc byte[
MessageFragmentHeader.MaxFragmentSize];
int written = GameMessageFragment.WriteSingleFragment(
destination,
fragmentSequence: 99,
queue: GameMessageGroup.ControlQueue,
gameMessageBytes: message);
Assert.Equal(expected.Length, written);
Assert.Equal(expected, destination.Slice(0, written).ToArray());
}
[Fact]
public void WriteSingleFragment_ShortDestination_Throws()
{
byte[] message = [1, 2, 3, 4];
Assert.Throws<ArgumentException>(
() => GameMessageFragment.WriteSingleFragment(
new byte[
MessageFragmentHeader.Size
+ message.Length - 1],
fragmentSequence: 1,
queue: GameMessageGroup.UIQueue,
gameMessageBytes: message));
}
}

View file

@ -1,4 +1,5 @@
using AcDream.Core.Net.Cryptography;
using AcDream.Core.Net.Messages;
using AcDream.Core.Net.Packets;
namespace AcDream.Core.Net.Tests.Packets;
@ -106,4 +107,197 @@ public class PacketCodecEncodeTests
Assert.Single(result.Packet!.Fragments);
Assert.Equal(new byte[] { 0xAA, 0xBB, 0xCC }, result.Packet.Fragments[0].Payload);
}
[Fact]
public void FinalizeInPlace_DirectFragment_MatchesOwnedFraming()
{
byte[] message = [0x58, 0xF6, 0, 0, 1, 2, 3, 4];
const uint fragmentSequence = 91;
var ownedFragment =
GameMessageFragment.BuildSingleFragment(
fragmentSequence,
GameMessageGroup.UIQueue,
message);
byte[] ownedBody =
GameMessageFragment.Serialize(ownedFragment);
var header = new PacketHeader
{
Sequence = 44,
Flags = PacketHeaderFlags.BlobFragments
| PacketHeaderFlags.EncryptedChecksum,
Id = 12,
Time = 13,
Iteration = 14,
};
byte[] seed = [1, 2, 3, 4];
byte[] expected = PacketCodec.Encode(
header,
ownedBody,
new IsaacRandom(seed));
Span<byte> actual = stackalloc byte[
PacketHeader.Size
+ MessageFragmentHeader.MaxFragmentSize];
int fragmentLength =
GameMessageFragment.WriteSingleFragment(
actual.Slice(PacketHeader.Size),
fragmentSequence,
GameMessageGroup.UIQueue,
message);
int datagramLength = PacketCodec.FinalizeInPlace(
header,
actual,
fragmentLength,
optionalLength: 0,
new IsaacRandom(seed));
Assert.Equal(expected.Length, datagramLength);
Assert.Equal(
expected,
actual.Slice(0, datagramLength).ToArray());
}
[Fact]
public void FinalizeInPlace_DirectAck_MatchesOwnedFraming()
{
const uint acknowledgedSequence = 0x12345678;
var header = new PacketHeader
{
Sequence = 22,
Flags = PacketHeaderFlags.AckSequence,
Id = 7,
};
byte[] body = new byte[sizeof(uint)];
System.Buffers.Binary.BinaryPrimitives
.WriteUInt32LittleEndian(
body,
acknowledgedSequence);
byte[] expected = PacketCodec.Encode(
header,
body,
outboundIsaac: null);
Span<byte> actual = stackalloc byte[
PacketHeader.Size + sizeof(uint)];
body.CopyTo(actual.Slice(PacketHeader.Size));
int datagramLength = PacketCodec.FinalizeInPlace(
header,
actual,
bodyLength: sizeof(uint),
optionalLength: sizeof(uint),
outboundIsaac: null);
Assert.Equal(expected, actual.Slice(0, datagramLength).ToArray());
}
[Fact]
public void FinalizeInPlace_MalformedFragment_DoesNotConsumeIsaac()
{
byte[] seed = [9, 8, 7, 6];
var header = new PacketHeader
{
Sequence = 33,
Flags = PacketHeaderFlags.BlobFragments
| PacketHeaderFlags.EncryptedChecksum,
Id = 5,
};
byte[] malformed = new byte[
PacketHeader.Size + MessageFragmentHeader.Size];
new MessageFragmentHeader
{
Count = 0,
TotalSize = MessageFragmentHeader.Size,
}.Pack(malformed.AsSpan(PacketHeader.Size));
var afterFailure = new IsaacRandom(seed);
Assert.Throws<ArgumentException>(
() => PacketCodec.FinalizeInPlace(
header,
malformed,
MessageFragmentHeader.Size,
optionalLength: 0,
afterFailure));
byte[] message = [1, 2, 3, 4];
Span<byte> actual = stackalloc byte[
PacketHeader.Size
+ MessageFragmentHeader.MaxFragmentSize];
int fragmentLength =
GameMessageFragment.WriteSingleFragment(
actual.Slice(PacketHeader.Size),
fragmentSequence: 1,
GameMessageGroup.UIQueue,
message);
int actualLength = PacketCodec.FinalizeInPlace(
header,
actual,
fragmentLength,
optionalLength: 0,
afterFailure);
var owned = GameMessageFragment.BuildSingleFragment(
fragmentSequence: 1,
GameMessageGroup.UIQueue,
message);
byte[] expected = PacketCodec.Encode(
header,
GameMessageFragment.Serialize(owned),
new IsaacRandom(seed));
Assert.Equal(
expected,
actual.Slice(0, actualLength).ToArray());
}
[Fact]
public void DirectSingleFragmentFraming_WarmPath_AllocatesNothing()
{
byte[] message = [1, 2, 3, 4, 5, 6, 7, 8];
var header = new PacketHeader
{
Sequence = 1,
Flags = PacketHeaderFlags.BlobFragments
| PacketHeaderFlags.EncryptedChecksum,
Id = 2,
};
var isaac = new IsaacRandom([4, 3, 2, 1]);
Span<byte> datagram = stackalloc byte[
PacketHeader.Size
+ MessageFragmentHeader.MaxFragmentSize];
WriteDirect(datagram, header, isaac, message);
long before = GC.GetAllocatedBytesForCurrentThread();
int checksum = 0;
for (int iteration = 0; iteration < 1_000; iteration++)
{
checksum += WriteDirect(
datagram,
header,
isaac,
message);
}
long allocated =
GC.GetAllocatedBytesForCurrentThread() - before;
Assert.NotEqual(0, checksum);
Assert.Equal(0, allocated);
}
private static int WriteDirect(
Span<byte> datagram,
PacketHeader header,
IsaacRandom isaac,
ReadOnlySpan<byte> message)
{
int fragmentLength =
GameMessageFragment.WriteSingleFragment(
datagram.Slice(PacketHeader.Size),
fragmentSequence: 3,
GameMessageGroup.UIQueue,
message);
return PacketCodec.FinalizeInPlace(
header,
datagram,
fragmentLength,
optionalLength: 0,
isaac);
}
}