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:
parent
629a5008c3
commit
10a69bf754
83 changed files with 958 additions and 2432 deletions
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue