From 293e6915395d5a5c28f69b9d82a5c35350307f5d Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sun, 25 Oct 2020 12:54:37 -0700 Subject: [PATCH] Changes Huffman to in-place & Adds CircularBuffer (#287) - [X] Adds CircularBuffer - [X] Updates Packet Encoding/Decoding - [X] Changes Huffman to in-place Bumps release version --- Projects/Server/Buffers/CircularBuffer.cs | 130 +++++++++++++ Projects/Server/Buffers/SpanReader.cs | 4 +- Projects/Server/Buffers/SpanWriter.cs | 4 +- Projects/Server/Network/NetState/NetState.cs | 112 ++++------- Projects/Server/Network/NetworkCompression.cs | 175 +++++++----------- Projects/Server/Network/Packet.cs | 2 +- 6 files changed, 237 insertions(+), 190 deletions(-) create mode 100644 Projects/Server/Buffers/CircularBuffer.cs diff --git a/Projects/Server/Buffers/CircularBuffer.cs b/Projects/Server/Buffers/CircularBuffer.cs new file mode 100644 index 000000000..b34240490 --- /dev/null +++ b/Projects/Server/Buffers/CircularBuffer.cs @@ -0,0 +1,130 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: CircularBuffer.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace System.Buffers +{ + public readonly ref struct CircularBuffer where T : struct + { + private readonly Span _first; + private readonly Span _second; + + public int Length { get; } + + public CircularBuffer(ArraySegment[] buffers) : this(buffers[0], buffers[1]) + { + } + + public CircularBuffer(Span first, Span second) + { + _first = first; + _second = second; + Length = first.Length + second.Length; + } + + public T this[int index] + { + get + { + if (index < 0 || index > Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return index < _first.Length ? _first[index] : _second[_first.Length - index]; + } + set + { + if (index < 0 || index > Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (index < _first.Length) + { + _first[index] = value; + } + else + { + _second[_first.Length - index] = value; + } + } + } + + public void CopyFrom(ReadOnlySpan bytes) + { + var remaining = bytes.Length; + var offset = 0; + + if (remaining == 0) + { + return; + } + + for (int i = 0; i < 2; i++) + { + var buffer = i == 0 ? _first : _second; + + if (buffer.Length == 0) + { + continue; + } + + var sz = Math.Min(remaining, buffer.Length); + bytes.Slice(offset, sz).CopyTo(buffer); + + remaining -= sz; + offset += sz; + + if (remaining == 0) + { + return; + } + } + + throw new OutOfMemoryException(); + } + + public void CopyTo(Span bytes) + { + if (bytes.Length < Length) + { + throw new ArgumentOutOfRangeException(nameof(bytes.Length)); + } + + if (_first.Length > 0) + { + _first.CopyTo(bytes); + } + + if (_second.Length > 0) + { + _second.CopyTo(bytes.Slice(_first.Length)); + } + } + + public CircularBuffer Slice(int offset, int count) + { + var firstCount = Math.Min(count, _first.Length - offset); + var first = offset < _first.Length + ? _first.Slice(offset, firstCount) + : Span.Empty; + + var secondCount = offset > _first.Length ? count : count - firstCount; + var second = secondCount > 0 ? _second.Slice(Math.Max(0, offset - _first.Length), secondCount) : Span.Empty; + + return new CircularBuffer(first, second); + } + } +} diff --git a/Projects/Server/Buffers/SpanReader.cs b/Projects/Server/Buffers/SpanReader.cs index 66b246a12..1899b80d3 100644 --- a/Projects/Server/Buffers/SpanReader.cs +++ b/Projects/Server/Buffers/SpanReader.cs @@ -13,13 +13,13 @@ * along with this program. If not, see . * *************************************************************************/ -using System; using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; using System.Text; +using Server; -namespace Server.Buffers +namespace System.Buffers { ref struct SpanReader { diff --git a/Projects/Server/Buffers/SpanWriter.cs b/Projects/Server/Buffers/SpanWriter.cs index d61b2d352..ac88a392b 100644 --- a/Projects/Server/Buffers/SpanWriter.cs +++ b/Projects/Server/Buffers/SpanWriter.cs @@ -13,14 +13,14 @@ * along with this program. If not, see . * *************************************************************************/ -using System; using System.Buffers.Binary; using System.Data; using System.IO; using System.Runtime.CompilerServices; using System.Text; +using Server; -namespace Server.Buffers +namespace System.Buffers { public ref struct SpanWriter { diff --git a/Projects/Server/Network/NetState/NetState.cs b/Projects/Server/Network/NetState/NetState.cs index 0f3f2f681..da985d191 100644 --- a/Projects/Server/Network/NetState/NetState.cs +++ b/Projects/Server/Network/NetState/NetState.cs @@ -33,12 +33,12 @@ namespace Server.Network { public delegate void NetStateCreatedCallback(NetState ns); - public delegate void EncodePacket(ReadOnlySpan inputBuffer, CircularBufferWriter outputBuffer); + public delegate void EncodePacket(CircularBuffer buffer, ref int length); public partial class NetState : IComparable { - private static int IncomingPipeSize = 1024 * 64; - private static int OutgoingPipeSize = 1024 * 256; + private static int RecvPipeSize = 1024 * 64; + private static int SendPipeSize = 1024 * 256; private static int GumpCap = 512; private static int HuePickerCap = 512; private static int MenuCap = 512; @@ -51,13 +51,13 @@ namespace Server.Network private readonly string m_ToString; private int m_Disposing; private ClientVersion m_Version; - private byte[] m_IncomingBuffer; - private Pipe m_IncomingPipe; - private byte[] m_OutgoingBuffer; - private Pipe m_OutgoingPipe; + private byte[] _recvBuffer; + private Pipe _recvPipe; + private byte[] _sendBuffer; + private Pipe _sendPipe; private long m_NextCheckActivity; private volatile bool m_Running; - private Thread _sendThread; + private readonly Thread _sendThread; private volatile EncodePacket _packetDecoder; private volatile EncodePacket _packetEncoder; @@ -66,8 +66,8 @@ namespace Server.Network public static void Configure() { - IncomingPipeSize = ServerConfiguration.GetOrUpdateSetting("netstate.incomingPipeSize", IncomingPipeSize); - OutgoingPipeSize = ServerConfiguration.GetOrUpdateSetting("netstate.outgoingPipeSize", OutgoingPipeSize); + RecvPipeSize = ServerConfiguration.GetOrUpdateSetting("netstate.recvPipeSize", RecvPipeSize); + SendPipeSize = ServerConfiguration.GetOrUpdateSetting("netstate.sendPipeSize", SendPipeSize); GumpCap = ServerConfiguration.GetOrUpdateSetting("netstate.gumpCap", GumpCap); HuePickerCap = ServerConfiguration.GetOrUpdateSetting("netstate.huePickerCap", HuePickerCap); MenuCap = ServerConfiguration.GetOrUpdateSetting("netstate.menuCap", MenuCap); @@ -88,10 +88,10 @@ namespace Server.Network HuePickers = new List(); Menus = new List(); Trades = new List(); - m_IncomingBuffer = new byte[IncomingPipeSize]; - m_IncomingPipe = new Pipe(m_IncomingBuffer); - m_OutgoingBuffer = new byte[OutgoingPipeSize]; - m_OutgoingPipe = new Pipe(m_OutgoingBuffer); + _recvBuffer = new byte[RecvPipeSize]; + _recvPipe = new Pipe(_recvBuffer); + _sendBuffer = new byte[SendPipeSize]; + _sendPipe = new Pipe(_sendBuffer); m_NextCheckActivity = Core.TickCount + 30000; _sendThread = sendThread ?? Core.Thread; @@ -370,7 +370,9 @@ namespace Server.Network NetworkState.Resume(ref m_NetworkState); } - public virtual void Send(Span buffer) + public Pipe.Result GetAvailableSendPipe() => _recvPipe.Writer.GetAvailable(); + + public virtual void Send(CircularBuffer buffer, int length) { if (Connection == null || BlockAllPackets || buffer.Length == 0) { @@ -387,25 +389,10 @@ namespace Server.Network #endif } - var writer = m_OutgoingPipe.Writer; - try { - var result = writer.GetAvailable(); - int length; - if (PacketEncoder != null) - { - var bufferWriter = new CircularBufferWriter(result.Buffer); - PacketEncoder?.Invoke(buffer, bufferWriter); - length = bufferWriter.Position; - } - else - { - result.CopyFrom(buffer); - length = buffer.Length; - } - - writer.Advance((uint)length); + _packetEncoder?.Invoke(buffer, ref length); + _sendPipe.Writer.Advance((uint)length); } catch (Exception ex) { @@ -435,7 +422,7 @@ namespace Server.Network #endif } - var writer = m_OutgoingPipe.Writer; + var writer = _sendPipe.Writer; try { @@ -490,7 +477,7 @@ namespace Server.Network private async void SendTask(object state) { - var reader = m_OutgoingPipe.Reader; + var reader = _sendPipe.Reader; try { @@ -527,19 +514,16 @@ namespace Server.Network } } - private int DecodePacket(ReadOnlySpan input, ArraySegment[] output) + private void DecodePacket(ArraySegment[] buffer, ref int length) { - var writer = new CircularBufferWriter(output); - PacketDecoder(input, writer); - return writer.Position; + CircularBuffer cBuffer = new CircularBuffer(buffer); + _packetDecoder?.Invoke(cBuffer, ref length); } private async void RecvTask(object state) { var socket = Connection; - var writer = m_IncomingPipe.Writer; - - byte[] encodingBuffer = null; + var writer = _recvPipe.Writer; try { @@ -557,33 +541,13 @@ namespace Server.Network continue; } - int bytesWritten; - - if (PacketDecoder != null) + var bytesWritten = await socket.ReceiveAsync(result.Buffer, SocketFlags.None); + if (bytesWritten <= 0) { - encodingBuffer ??= ArrayPool.Shared.Rent(0x10000); - bytesWritten = await socket.ReceiveAsync(encodingBuffer, SocketFlags.None); - if (bytesWritten <= 0) - { - break; - } - bytesWritten = DecodePacket(encodingBuffer.AsSpan(0, bytesWritten), result.Buffer); + break; } - else - { - if (encodingBuffer != null) - { - var returnBuffer = encodingBuffer; - encodingBuffer = null; - ArrayPool.Shared.Return(returnBuffer); - } - bytesWritten = await socket.ReceiveAsync(result.Buffer, SocketFlags.None); - if (bytesWritten <= 0) - { - break; - } - } + DecodePacket(result.Buffer, ref bytesWritten); writer.Advance((uint)bytesWritten); m_NextCheckActivity = Core.TickCount + 90000; @@ -600,10 +564,6 @@ namespace Server.Network } finally { - if (encodingBuffer != null) - { - ArrayPool.Shared.Return(encodingBuffer); - } Dispose(); } } @@ -627,7 +587,7 @@ namespace Server.Network try { - var reader = m_IncomingPipe.Reader; + var reader = _recvPipe.Reader; // Process as many packets as we can synchronously while (true) @@ -670,7 +630,7 @@ namespace Server.Network { if (Connection != null) { - m_OutgoingPipe.Writer.Flush(); + _sendPipe.Writer.Flush(); } } @@ -755,7 +715,7 @@ namespace Server.Network return; } - m_OutgoingPipe.Writer.Close(); + _sendPipe.Writer.Close(); try { @@ -793,10 +753,10 @@ namespace Server.Network ns.m_Running = false; ns.Connection = null; - ns.m_IncomingBuffer = null; - ns.m_IncomingPipe = null; - ns.m_OutgoingBuffer = null; - ns.m_OutgoingPipe = null; + ns._recvBuffer = null; + ns._recvPipe = null; + ns._sendBuffer = null; + ns._sendPipe = null; ns.Gumps.Clear(); ns.Menus.Clear(); ns.HuePickers.Clear(); diff --git a/Projects/Server/Network/NetworkCompression.cs b/Projects/Server/Network/NetworkCompression.cs index 946df2b4e..7c78d0d2c 100644 --- a/Projects/Server/Network/NetworkCompression.cs +++ b/Projects/Server/Network/NetworkCompression.cs @@ -12,7 +12,7 @@ namespace Server.Network private const int ValueIndex = 1; // UO packets may not exceed 64kb in length - private const int BufferSize = 0x10000; + public const int BufferSize = 0x10000; // Optimal compression ratio is 2 / 8; worst compression ratio is 11 / 8 private const int MinimalCodeLength = 2; @@ -61,170 +61,127 @@ namespace Server.Network 0x4, 0x00D }; - public static void Compress(ReadOnlySpan input, CircularBufferWriter output) + public static void Compress(CircularBuffer buffer, ref int length) { - int inputCapacity = input.Length; + length = Compress(buffer, length, buffer); + } - if (inputCapacity > DefiniteOverflow) + public static int Compress(CircularBuffer input, int inputLength, CircularBuffer output) + { + if (inputLength > DefiniteOverflow) { - return; + return 0; } int bitCount = 0; int bitValue = 0; int inputIdx = 0; + int outputIdx = 0; - while (inputIdx < inputCapacity) + while (inputIdx < inputLength) { int i = input[inputIdx++] << 1; bitCount += _huffmanTable[i]; - bitValue <<= _huffmanTable[i]; - bitValue |= _huffmanTable[i + 1]; + bitValue = (bitValue << _huffmanTable[i]) | _huffmanTable[i + 1]; while (bitCount >= 8) { bitCount -= 8; - if (output.Length < output.Position + 1) + if (output.Length < outputIdx + 1) { - return; + return 0; } - output.Write((byte)(bitValue >> bitCount)); + output[outputIdx++] = (byte)(bitValue >> bitCount); } } // terminal code bitCount += _huffmanTable[0x200]; - bitValue <<= _huffmanTable[0x200]; - bitValue |= _huffmanTable[0x201]; + bitValue = (bitValue << _huffmanTable[0x200]) | _huffmanTable[0x201]; // align on byte boundary if ((bitCount & 7) != 0) { - bitValue <<= (8 - (bitCount & 7)); - bitCount += (8 - (bitCount & 7)); + bitValue <<= 8 - (bitCount & 7); + bitCount += 8 - (bitCount & 7); } while (bitCount >= 8) { bitCount -= 8; - if (output.Length < output.Position + 1) + if (output.Length < outputIdx + 1) { - return; + return 0; } - output.Write((byte)(bitValue >> bitCount)); + output[outputIdx++] = (byte)(bitValue >> bitCount); } + + return outputIdx; } - public static unsafe void Compress( - ReadOnlySpan input, int offset, int count, Span output, out int length - ) + public static int Compress(ReadOnlySpan input, Span output) { - if (input == null) + if (input.Length > DefiniteOverflow) { - throw new ArgumentNullException(nameof(input)); + return 0; } - if (offset < 0 || offset >= input.Length) + int bitCount = 0; + int bitValue = 0; + + int inputIdx = 0; + int outputIdx = 0; + + while (inputIdx < input.Length) { - throw new ArgumentOutOfRangeException(nameof(offset)); - } + int i = input[inputIdx++] << 1; - if (count < 0 || count > input.Length) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } + bitCount += _huffmanTable[i]; + bitValue = (bitValue << _huffmanTable[i]) | _huffmanTable[i + 1]; - if (input.Length - offset < count) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - length = 0; - - if (count > DefiniteOverflow) - { - return; - } - - var bitCount = 0; - var bitValue = 0; - - fixed (int* pTable = _huffmanTable) - { - fixed (byte* pInputBuffer = input) + while (bitCount >= 8) { - byte* pInput = pInputBuffer + offset, pInputEnd = pInput + count; + bitCount -= 8; - fixed (byte* pOutputBuffer = output) + if (output.Length < outputIdx + 1) { - byte* pOutput = pOutputBuffer, pOutputEnd = pOutput + BufferSize; - - int* pEntry; - while (pInput < pInputEnd) - { - pEntry = &pTable[*pInput++ << 1]; - - bitCount += pEntry[CountIndex]; - - bitValue <<= pEntry[CountIndex]; - bitValue |= pEntry[ValueIndex]; - - while (bitCount >= 8) - { - bitCount -= 8; - - if (pOutput < pOutputEnd) - { - *pOutput++ = (byte)(bitValue >> bitCount); - } - else - { - length = 0; - return; - } - } - } - - // terminal code - pEntry = &pTable[0x200]; - - bitCount += pEntry[CountIndex]; - - bitValue <<= pEntry[CountIndex]; - bitValue |= pEntry[ValueIndex]; - - // align on byte boundary - if ((bitCount & 7) != 0) - { - bitValue <<= 8 - (bitCount & 7); - bitCount += 8 - (bitCount & 7); - } - - while (bitCount >= 8) - { - bitCount -= 8; - - if (pOutput < pOutputEnd) - { - *pOutput++ = (byte)(bitValue >> bitCount); - } - else - { - length = 0; - return; - } - } - - length = (int)(pOutput - pOutputBuffer); + return 0; } + + output[outputIdx++] = (byte)(bitValue >> bitCount); } } + + // terminal code + bitCount += _huffmanTable[0x200]; + bitValue = (bitValue << _huffmanTable[0x200]) | _huffmanTable[0x201]; + + // align on byte boundary + if ((bitCount & 7) != 0) + { + bitValue <<= 8 - (bitCount & 7); + bitCount += 8 - (bitCount & 7); + } + + while (bitCount >= 8) + { + bitCount -= 8; + + if (output.Length < outputIdx + 1) + { + return 0; + } + + output[outputIdx++] = (byte)(bitValue >> bitCount); + } + + return outputIdx; } } } diff --git a/Projects/Server/Network/Packet.cs b/Projects/Server/Network/Packet.cs index 3a7b0177d..9676e6cc8 100644 --- a/Projects/Server/Network/Packet.cs +++ b/Projects/Server/Network/Packet.cs @@ -194,7 +194,7 @@ namespace Server.Network if (compress) { var compressorBuffer = ArrayPool.Shared.Rent(CompressorBufferSize); - NetworkCompression.Compress(m_CompiledBuffer, 0, length, compressorBuffer, out var compressedLength); + var compressedLength = NetworkCompression.Compress(m_CompiledBuffer.AsSpan(0, length), compressorBuffer); if (length <= 0) {