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:
parent
e928c5dd02
commit
41c1a59392
7 changed files with 485 additions and 31 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue