feat: Adds a memory mirrored ring buffer for networking. (#1533)

## Breaking Changes

Incoming packet registration signature has changed to:
```cs
delegate* void OnReceiveCallback(NetState state, SpanReader reader, int packetLength);

IncomingPackets.Register(int packetID, int length, bool ingame, OnReceiveCallback onReceive);
```

For example, an incoming packet handler signature would now look like this:
```cs
public static void SomeIncomingPacket(NetState state, SpanReader reader, int packetLength)
{
    // Parse the data
}
```

## Summary

Updates the network Pipe class to use a mirrored memory technique. This technique involves mapping the same physical memory to two contiguous virtual memory spaces so the byte buffer appears duplicated. This allows writing to a double-sized array to wrap around without the need for the `CircularBuffer` classes.

In practice this allows us to use `Span<byte>` as if the buffer was a regular array.


### Bug Fixes

- [X] Fixes bad fixed length string parsing
This commit is contained in:
Kamron Batman 2023-10-09 00:57:53 -07:00 committed by GitHub
parent 629a5008c3
commit 10a69bf754
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
83 changed files with 958 additions and 2432 deletions

View file

@ -34,8 +34,8 @@ namespace Server.Network;
public delegate void NetStateCreatedCallback(NetState ns);
public delegate void DecodePacket(CircularBuffer<byte> buffer, ref int length);
public delegate void EncodePacket(ReadOnlySpan<byte> inputBuffer, CircularBuffer<byte> outputBuffer, out int length);
public delegate void DecodePacket(Span<byte> buffer, ref int length);
public delegate int EncodePacket(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer);
public partial class NetState : IComparable<NetState>
{
@ -123,8 +123,8 @@ public partial class NetState : IComparable<NetState>
HuePickers = new List<HuePicker>();
Menus = new List<IMenu>();
Trades = new List<SecureTrade>();
RecvPipe = new Pipe<byte>(GC.AllocateUninitializedArray<byte>(RecvPipeSize));
SendPipe = new Pipe<byte>(GC.AllocateUninitializedArray<byte>(SendPipeSize));
RecvPipe = new Pipe(RecvPipeSize);
SendPipe = new Pipe(SendPipeSize);
_nextActivityCheck = Core.TickCount + 30000;
ConnectedOn = Core.Now;
@ -202,9 +202,9 @@ public partial class NetState : IComparable<NetState>
public bool Seeded { get; set; }
public Pipe<byte> RecvPipe { get; }
public Pipe RecvPipe { get; }
public Pipe<byte> SendPipe { get; }
public Pipe SendPipe { get; }
public bool Running => _running;
@ -454,7 +454,7 @@ public partial class NetState : IComparable<NetState>
public override string ToString() => _toString;
public bool GetSendBuffer(out CircularBuffer<byte> cBuffer)
public bool GetSendBuffer(out Span<byte> buffer)
{
#if THREADGUARD
if (Thread.CurrentThread != Core.Thread)
@ -466,10 +466,8 @@ public partial class NetState : IComparable<NetState>
return;
}
#endif
var result = SendPipe.Writer.TryGetMemory();
cBuffer = new CircularBuffer<byte>(result.Buffer);
return !(result.IsClosed || result.Length <= 0);
buffer = SendPipe.Writer.AvailableToWrite();
return !(SendPipe.Writer.IsClosed || buffer.Length <= 0);
}
public void Send(ReadOnlySpan<byte> span)
@ -497,16 +495,16 @@ public partial class NetState : IComparable<NetState>
if (_packetEncoder != null)
{
_packetEncoder(span, buffer, out length);
length = _packetEncoder(span, buffer);
}
else
{
buffer.CopyFrom(span);
span.CopyTo(buffer);
}
if (PacketLogging)
{
LogPacket(span, ReadOnlySpan<byte>.Empty, span.Length, false);
LogPacket(span, false);
}
SendPipe.Writer.Advance((uint)length);
@ -545,7 +543,7 @@ public partial class NetState : IComparable<NetState>
}
}
private void LogPacket(ReadOnlySpan<byte> first, ReadOnlySpan<byte> second, int totalLength, bool incoming)
private void LogPacket(ReadOnlySpan<byte> buffer, bool incoming)
{
try
{
@ -557,8 +555,8 @@ public partial class NetState : IComparable<NetState>
const string outgoingStr = "Server -> Client";
using var sw = new StreamWriter(logPath, true);
sw.WriteLine($"{Core.Now:HH:mm:ss.ffff}: {(incoming ? incomingStr : outgoingStr)} 0x{first[0]:X2} (Length: {totalLength})");
sw.FormatBuffer(first, second, totalLength);
sw.WriteLine($"{Core.Now:HH:mm:ss.ffff}: {(incoming ? incomingStr : outgoingStr)} 0x{buffer[0]:X2} (Length: {buffer.Length})");
sw.FormatBuffer(buffer);
sw.WriteLine();
sw.WriteLine();
}
@ -587,15 +585,15 @@ public partial class NetState : IComparable<NetState>
// Process as many packets as we can synchronously
while (_running && _parserState != ParserState.Error && _protocolState != ProtocolState.Error)
{
var result = reader.TryRead();
var length = result.Length;
var buffer = reader.AvailableToRead();
var length = buffer.Length;
if (length <= 0)
{
break;
}
var packetReader = new CircularBufferReader(result.Buffer);
var packetReader = new SpanReader(buffer);
var packetId = packetReader.ReadByte();
int packetLength = length;
@ -744,8 +742,6 @@ public partial class NetState : IComparable<NetState>
break;
}
}
reader.Commit();
}
catch (Exception ex)
{
@ -771,7 +767,7 @@ public partial class NetState : IComparable<NetState>
* length is the total buffer length. We might be able to use packetReader.Capacity() instead.
* packetLength is the length of the packet that this function actually found.
*/
private unsafe ParserState HandlePacket(CircularBufferReader packetReader, byte packetId, out int packetLength)
private unsafe ParserState HandlePacket(SpanReader packetReader, byte packetId, out int packetLength)
{
PacketHandler handler = IncomingPackets.GetHandler(packetId);
int length = packetReader.Length;
@ -842,7 +838,7 @@ public partial class NetState : IComparable<NetState>
if (PacketLogging)
{
LogPacket(packetReader.First, packetReader.Second, packetLength, true);
LogPacket(packetReader.Buffer[..packetLength], true);
}
handler.OnReceive(this, packetReader, packetLength);
@ -861,12 +857,10 @@ public partial class NetState : IComparable<NetState>
return true;
}
SendPipe.Writer.Flush();
var reader = SendPipe.Reader;
var result = reader.TryRead();
var buffer = reader.AvailableToRead();
if (result.IsClosed || result.Length == 0)
if (reader.IsClosed || buffer.Length == 0)
{
return true;
}
@ -875,7 +869,7 @@ public partial class NetState : IComparable<NetState>
try
{
bytesWritten = Connection.Send(result.Buffer, SocketFlags.None);
bytesWritten = Connection.Send(buffer, SocketFlags.None);
}
catch (SocketException ex)
{
@ -897,21 +891,20 @@ public partial class NetState : IComparable<NetState>
reader.Advance((uint)bytesWritten);
}
return bytesWritten == result.Length;
return bytesWritten == buffer.Length;
}
private void DecodePacket(ArraySegment<byte>[] buffer, ref int length)
private void DecodePacket(Span<byte> buffer, ref int length)
{
CircularBuffer<byte> cBuffer = new CircularBuffer<byte>(buffer);
_packetDecoder?.Invoke(cBuffer, ref length);
_packetDecoder?.Invoke(buffer, ref length);
}
private void ReceiveData()
{
var writer = RecvPipe.Writer;
var result = writer.TryGetMemory();
var buffer = writer.AvailableToWrite();
if (result.IsClosed || result.Length == 0)
if (writer.IsClosed || buffer.Length == 0)
{
return;
}
@ -920,7 +913,7 @@ public partial class NetState : IComparable<NetState>
try
{
bytesWritten = Connection.Receive(result.Buffer, SocketFlags.None);
bytesWritten = Connection.Receive(buffer, SocketFlags.None);
}
catch (SocketException ex)
{
@ -943,7 +936,7 @@ public partial class NetState : IComparable<NetState>
return;
}
DecodePacket(result.Buffer, ref bytesWritten);
DecodePacket(buffer, ref bytesWritten);
writer.Advance((uint)bytesWritten);
_nextActivityCheck = Core.TickCount + 90000;
@ -1042,6 +1035,28 @@ public partial class NetState : IComparable<NetState>
}
}
public void Trace(ReadOnlySpan<byte> buffer)
{
// We don't have data, so nothing to trace
if (buffer.Length == 0)
{
return;
}
try
{
using var sw = new StreamWriter("unhandled-packets.log", true);
sw.WriteLine("Client: {0}: Unhandled packet 0x{1:X2}", this, buffer[0]);
sw.FormatBuffer(buffer);
sw.WriteLine();
sw.WriteLine();
}
catch
{
// ignored
}
}
public static void TraceException(Exception ex)
{
try
@ -1148,6 +1163,8 @@ public partial class NetState : IComparable<NetState>
Connection.Close();
_handle.Free();
RecvPipe.Dispose();
SendPipe.Dispose();
Mobile = null;