feat(network)!: add v2 framing, pooled buffers and bounded dispatch
Publish UPM package / publish (push) Successful in 2s
Publish NuGet packages / publish (push) Successful in 3m5s

This commit is contained in:
2026-09-29 10:14:47 +08:00
parent c7c45b26f8
commit 93c2b8520b
36 changed files with 752 additions and 398 deletions
@@ -1,6 +1,7 @@
#nullable enable
using System;
using System.Buffers.Binary;
using System.Buffers;
using System.IO;
using System.Net.Sockets;
using System.Net.Security;
@@ -10,7 +11,7 @@ using Cysharp.Threading.Tasks;
namespace ShrinkNetwork
{
public sealed class ShrinkTcpClientTransport : IShrinkNetworkAsyncTransport
public sealed class ShrinkTcpClientTransport : IShrinkNetworkMemoryTransport
{
private readonly string _host;
private readonly int _port;
@@ -86,7 +87,9 @@ namespace ShrinkNetwork
SendAsync(sessionId, packetData).Forget();
}
public UniTask SendAsync(long sessionId, byte[] packetData)
public UniTask SendAsync(long sessionId, byte[] packetData) => SendAsync(sessionId, (ReadOnlyMemory<byte>)(packetData ?? Array.Empty<byte>()));
public UniTask SendAsync(long sessionId, ReadOnlyMemory<byte> packetData, CancellationToken cancellationToken = default)
{
if (!IsStarted)
throw new InvalidOperationException("Transport is not started.");
@@ -94,11 +97,11 @@ namespace ShrinkNetwork
throw new InvalidOperationException($"Unsupported session id {sessionId}. This transport only supports {_sessionId}.");
if (_client == null || !_client.Connected || _stream == null)
throw new InvalidOperationException("TCP client is not connected.");
if ((packetData?.Length ?? 0) > _maxPacketSize)
throw new InvalidOperationException($"TCP packet is too large. Size={(packetData?.Length ?? 0)}, Limit={_maxPacketSize}.");
if (packetData.Length > _maxPacketSize)
throw new InvalidOperationException($"TCP packet is too large. Size={packetData.Length}, Limit={_maxPacketSize}.");
return SendInternalAsync(_stream, _sendLock, packetData ?? Array.Empty<byte>(),
_cts?.Token ?? CancellationToken.None);
return SendInternalAsync(_stream, _sendLock, packetData,
cancellationToken.CanBeCanceled ? cancellationToken : _cts?.Token ?? CancellationToken.None);
}
private async UniTaskVoid ConnectAsync(CancellationToken cancellationToken)
@@ -209,18 +212,20 @@ namespace ShrinkNetwork
ex.SocketErrorCode == SocketError.Shutdown;
}
private static async UniTask SendInternalAsync(Stream stream, SemaphoreSlim sendLock, byte[] packetData,
private static async UniTask SendInternalAsync(Stream stream, SemaphoreSlim sendLock, ReadOnlyMemory<byte> packetData,
CancellationToken cancellationToken)
{
await sendLock.WaitAsync(cancellationToken);
try
{
var header = new byte[4];
BinaryPrimitives.WriteInt32LittleEndian(header, packetData.Length);
await stream.WriteAsync(header, cancellationToken);
if (packetData.Length > 0)
await stream.WriteAsync(packetData, cancellationToken);
await stream.FlushAsync(cancellationToken);
var frame = ArrayPool<byte>.Shared.Rent(checked(4 + packetData.Length));
try
{
BinaryPrimitives.WriteInt32LittleEndian(frame, packetData.Length);
packetData.CopyTo(frame.AsMemory(4));
await stream.WriteAsync(frame.AsMemory(0, 4 + packetData.Length), cancellationToken);
}
finally { ArrayPool<byte>.Shared.Return(frame, clearArray: true); }
}
finally
{