From: Forest Date: Tue, 17 Jul 2018 18:37:42 +0000 (-0700) Subject: Add a recyclable message buffer to curb allocations and copies X-Git-Tag: 1.0.0~88 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=f631406fd1c5a6c38c000866c89e1ab01edfeb96;p=rhonda%2Fimpostor.hazel.git Add a recyclable message buffer to curb allocations and copies --- diff --git a/Hazel/BinaryWriterExtensions.cs b/Hazel/BinaryWriterExtensions.cs new file mode 100644 index 0000000..d5aeb22 --- /dev/null +++ b/Hazel/BinaryWriterExtensions.cs @@ -0,0 +1,65 @@ +using System.IO; + +namespace Hazel +{ + /// + public static class BinaryWriterExtensions + { + /// + public static void WritePacked(this BinaryWriter writer, uint value) + { + do + { + byte b = (byte)(value & 0xFF); + if (value >= 0x80) + { + b |= 0x80; + } + + writer.Write(b); + value >>= 7; + } while (value > 0); + } + + /// + public static uint ReadPackedUInt32(this BinaryReader reader) + { + bool readMore = true; + int shift = 0; + uint output = 0; + + while (readMore) + { + byte b = reader.ReadByte(); + if (b >= 0x80) + { + readMore = true; + b ^= 0x80; + } + else + { + readMore = false; + } + + output |= (uint)(b << shift); + shift += 7; + } + + return output; + } + + /// + public static void WriteBytesFull(this BinaryWriter writer, byte[] bytes) + { + writer.WritePacked((uint)bytes.Length); + writer.Write(bytes); + } + + /// + public static byte[] ReadBytesAndSize(this BinaryReader reader) + { + int len = (int)reader.ReadPackedUInt32(); + return reader.ReadBytes(len); + } + } +} \ No newline at end of file diff --git a/Hazel/Connection.cs b/Hazel/Connection.cs index 12c35dd..751795a 100644 --- a/Hazel/Connection.cs +++ b/Hazel/Connection.cs @@ -143,6 +143,20 @@ namespace Hazel State = ConnectionState.NotConnected; } + /// + /// Sends a number of bytes to the end point of the connection using the specified . + /// + /// The message to send. + /// + /// + /// + /// The sendOptions parameter is only a request to use those options and the actual method used to send the + /// data is up to the implementation. There are circumstances where this parameter may be ignored but in + /// general any implementer should aim to always follow the user's request. + /// + /// + public abstract void Send(MessageWriter msg); + /// /// Sends a number of bytes to the end point of the connection using the specified . /// diff --git a/Hazel/Hazel.csproj b/Hazel/Hazel.csproj index 98ec8da..24e67e9 100644 --- a/Hazel/Hazel.csproj +++ b/Hazel/Hazel.csproj @@ -49,6 +49,7 @@ + @@ -61,6 +62,7 @@ + diff --git a/Hazel/MessageWriter.cs b/Hazel/MessageWriter.cs new file mode 100644 index 0000000..83902d9 --- /dev/null +++ b/Hazel/MessageWriter.cs @@ -0,0 +1,86 @@ +using System; +using System.IO; + +namespace Hazel +{ + /// + public class MessageWriter : IRecyclable + { + public static int BufferSize = 64000; + private static readonly ObjectPool objectPool = new ObjectPool(() => new MessageWriter(BufferSize)); + + internal byte[] Buffer; + internal MemoryStream Stream; + public readonly BinaryWriter Writer; + + public SendOption SendOption { get; private set; } + + private long lastMessageStart; + + /// + public MessageWriter(int bufferSize) + { + this.Buffer = new byte[bufferSize]; + this.Stream = new MemoryStream(this.Buffer, true); + this.Writer = new BinaryWriter(this.Stream); + } + + /// + /// The option specifying how the message should be sent. + public static MessageWriter Get(SendOption sendOption = SendOption.None) + { + var output = objectPool.GetObject(); + output.SendOption = sendOption; + + switch (sendOption) + { + case SendOption.None: + output.Buffer[0] = (byte)sendOption; + output.Stream.Position = 1; // Type + break; + case SendOption.Reliable: + output.Buffer[0] = (byte)sendOption; + output.Stream.Position = 3; // Type + ID + break; + case SendOption.FragmentedReliable: + throw new NotImplementedException("Sry bruh"); + } + + return output; + } + + /// + public void StartMessage(byte typeFlag, uint targetObjId) + { + this.lastMessageStart = this.Stream.Position; + this.Stream.Position = this.lastMessageStart + 2; + + this.Writer.Write(typeFlag); + this.Writer.WritePacked(targetObjId); + } + + /// + public void EndMessage() + { + this.Writer.Flush(); + + ushort length = (ushort)(this.Stream.Position - this.lastMessageStart); + this.Buffer[this.lastMessageStart] = (byte)(length >> 8); + this.Buffer[this.lastMessageStart + 1] = (byte)(length & 0xFF); + } + + /// + public void CancelMessage() + { + this.Writer.Flush(); + this.Stream.Position = this.lastMessageStart; + } + + /// + public void Recycle() + { + this.Writer.Flush(); + objectPool.PutObject(this); + } + } +} diff --git a/Hazel/Udp/UdpClientConnection.cs b/Hazel/Udp/UdpClientConnection.cs index 2f5dfa1..4ea5c88 100644 --- a/Hazel/Udp/UdpClientConnection.cs +++ b/Hazel/Udp/UdpClientConnection.cs @@ -54,7 +54,7 @@ namespace Hazel.Udp } /// - protected override void WriteBytesToConnection(byte[] bytes) + protected override void WriteBytesToConnection(byte[] bytes, int length) { lock (stateLock) { @@ -67,7 +67,7 @@ namespace Hazel.Udp socket.BeginSendTo( bytes, 0, - bytes.Length, + length, SocketFlags.None, RemoteEndPoint, delegate (IAsyncResult result) diff --git a/Hazel/Udp/UdpConnection.Fragmented.cs b/Hazel/Udp/UdpConnection.Fragmented.cs index 209eb8d..8de05ba 100644 --- a/Hazel/Udp/UdpConnection.Fragmented.cs +++ b/Hazel/Udp/UdpConnection.Fragmented.cs @@ -54,13 +54,13 @@ namespace Hazel.Udp } //Pass fragment to reliable send code to ensure it will arrive - AttachReliableID(buffer, 5); + AttachReliableID(buffer, 5, buffer.Length); //Copy data into fragment Buffer.BlockCopy(data, FragmentSize * i, buffer, 7, buffer.Length - 7); //Send - WriteBytesToConnection(buffer); + WriteBytesToConnection(buffer, buffer.Length); } } diff --git a/Hazel/Udp/UdpConnection.Reliable.cs b/Hazel/Udp/UdpConnection.Reliable.cs index fd40398..1a1a3ff 100644 --- a/Hazel/Udp/UdpConnection.Reliable.cs +++ b/Hazel/Udp/UdpConnection.Reliable.cs @@ -182,7 +182,7 @@ namespace Hazel.Udp /// The buffer to attach to. /// The offset to attach at. /// The callback to make once the packet has been acknowledged. - void AttachReliableID(byte[] buffer, int offset, Action ackCallback = null) + void AttachReliableID(byte[] buffer, int offset, int sendLength, Action ackCallback = null) { //Find and reliable ID lock (reliableDataPacketsSent) @@ -225,7 +225,7 @@ namespace Hazel.Udp try { - WriteBytesToConnection(p.Data); + WriteBytesToConnection(p.Data, sendLength); } catch (InvalidOperationException e) { @@ -271,13 +271,13 @@ namespace Hazel.Udp bytes[0] = sendOption; //Add reliable ID - AttachReliableID(bytes, 1, ackCallback); + AttachReliableID(bytes, 1, bytes.Length, ackCallback); //Copy data into new array Buffer.BlockCopy(data, offset, bytes, bytes.Length - length, length); //Write to connection - WriteBytesToConnection(bytes); + WriteBytesToConnection(bytes, bytes.Length); Statistics.LogReliableSend(length, bytes.Length); } @@ -413,15 +413,16 @@ namespace Hazel.Udp /// The second identification byte. internal void SendAck(byte byte1, byte byte2) { - //Always reply with acknowledgement in order to stop the sender repeatedly sending it - WriteBytesToConnection( //TODO group acks together - new byte[] - { - (byte)UdpSendOption.Acknowledgement, - byte1, - byte2 - } - ); + byte[] bytes = new byte[] + { + (byte)UdpSendOption.Acknowledgement, + byte1, + byte2 + }; + + // Always reply with acknowledgement in order to stop the sender repeatedly sending it + // TODO: group acks together + WriteBytesToConnection(bytes, bytes.Length); } } } diff --git a/Hazel/Udp/UdpConnection.cs b/Hazel/Udp/UdpConnection.cs index 7f4c22d..54d4c99 100644 --- a/Hazel/Udp/UdpConnection.cs +++ b/Hazel/Udp/UdpConnection.cs @@ -27,7 +27,37 @@ namespace Hazel.Udp /// Writes the given bytes to the connection. /// /// The bytes to write. - protected abstract void WriteBytesToConnection(byte[] bytes); + protected abstract void WriteBytesToConnection(byte[] bytes, int length); + + /// + public override void Send(MessageWriter msg) + { + //Early check + if (State != ConnectionState.Connected) + throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?"); + + + //Inform keepalive not to send for a while + ResetKeepAliveTimer(); + + int length = (int)msg.Stream.Length; + switch (msg.SendOption) + { + case SendOption.Reliable: + AttachReliableID(msg.Buffer, 1, length); + WriteBytesToConnection(msg.Buffer, length); + Statistics.LogReliableSend(length - 3, length); + break; + + case SendOption.FragmentedReliable: + throw new NotImplementedException("Not yet"); + + default: + WriteBytesToConnection(msg.Buffer, length); + Statistics.LogUnreliableSend(length - 1, length);; + break; + } + } /// /// @@ -199,7 +229,7 @@ namespace Hazel.Udp Buffer.BlockCopy(data, offset, bytes, bytes.Length - length, length); //Write to connection - WriteBytesToConnection(bytes); + WriteBytesToConnection(bytes, bytes.Length); Statistics.LogUnreliableSend(length, bytes.Length); } diff --git a/Hazel/Udp/UdpConnectionListener.cs b/Hazel/Udp/UdpConnectionListener.cs index cb17c70..22bdbbc 100644 --- a/Hazel/Udp/UdpConnectionListener.cs +++ b/Hazel/Udp/UdpConnectionListener.cs @@ -183,14 +183,14 @@ namespace Hazel.Udp /// /// The bytes to send. /// The endpoint to send to. - internal void SendData(byte[] bytes, EndPoint endPoint) + internal void SendData(byte[] bytes, int length, EndPoint endPoint) { try { listener.BeginSendTo( bytes, 0, - bytes.Length, + length, SocketFlags.None, endPoint, delegate (IAsyncResult result) diff --git a/Hazel/Udp/UdpServerConnection.cs b/Hazel/Udp/UdpServerConnection.cs index 71bd8f6..1f47e6f 100644 --- a/Hazel/Udp/UdpServerConnection.cs +++ b/Hazel/Udp/UdpServerConnection.cs @@ -45,7 +45,7 @@ namespace Hazel.Udp } /// - protected override void WriteBytesToConnection(byte[] bytes) + protected override void WriteBytesToConnection(byte[] bytes, int length) { lock (stateLock) { @@ -53,7 +53,7 @@ namespace Hazel.Udp throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?"); } - Listener.SendData(bytes, RemoteEndPoint); + Listener.SendData(bytes, length, RemoteEndPoint); } ///