]> git.deb.at Git - rhonda/impostor.hazel.git/commitdiff
Add a recyclable message buffer to curb allocations and copies
authorForest <chocozilla@gmail.com>
Tue, 17 Jul 2018 18:37:42 +0000 (11:37 -0700)
committerForest <chocozilla@gmail.com>
Tue, 17 Jul 2018 18:37:42 +0000 (11:37 -0700)
Hazel/BinaryWriterExtensions.cs [new file with mode: 0644]
Hazel/Connection.cs
Hazel/Hazel.csproj
Hazel/MessageWriter.cs [new file with mode: 0644]
Hazel/Udp/UdpClientConnection.cs
Hazel/Udp/UdpConnection.Fragmented.cs
Hazel/Udp/UdpConnection.Reliable.cs
Hazel/Udp/UdpConnection.cs
Hazel/Udp/UdpConnectionListener.cs
Hazel/Udp/UdpServerConnection.cs

diff --git a/Hazel/BinaryWriterExtensions.cs b/Hazel/BinaryWriterExtensions.cs
new file mode 100644 (file)
index 0000000..d5aeb22
--- /dev/null
@@ -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
index 12c35dd9a96319cfb8abdf1b3594eaee5bfa9dee..751795a7d3dcea96f7b70dff9d7721621af7b279 100644 (file)
@@ -143,6 +143,20 @@ namespace Hazel
             State = ConnectionState.NotConnected;
         }
 
+        /// <summary>
+        ///     Sends a number of bytes to the end point of the connection using the specified <see cref="SendOption"/>.
+        /// </summary>
+        /// <param name="msg">The message to send.</param>
+        /// <remarks>
+        ///     <include file="DocInclude/common.xml" path="docs/item[@name='Connection_SendBytes_General']/*" />
+        ///     <para>
+        ///         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.
+        ///     </para>
+        /// </remarks>
+        public abstract void Send(MessageWriter msg);
+
         /// <summary>
         ///     Sends a number of bytes to the end point of the connection using the specified <see cref="SendOption"/>.
         /// </summary>
index 98ec8dae4daff54846c37e43d90aa4f942a05249..24e67e93a595352a1e50e651e8b4ebd1bc466038 100644 (file)
@@ -49,6 +49,7 @@
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="BinaryWriterExtensions.cs" />
     <Compile Include="Connection.cs" />
     <Compile Include="ConnectionEndPoint.cs" />
     <Compile Include="ConnectionListener.cs" />
@@ -61,6 +62,7 @@
     <Compile Include="NetworkConnection.cs" />
     <Compile Include="NetworkConnectionListener.cs" />
     <Compile Include="NetworkEndPoint.cs" />
+    <Compile Include="MessageWriter.cs" />
     <Compile Include="NewConnectionEventArgs.cs" />
     <Compile Include="ObjectPool.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
diff --git a/Hazel/MessageWriter.cs b/Hazel/MessageWriter.cs
new file mode 100644 (file)
index 0000000..83902d9
--- /dev/null
@@ -0,0 +1,86 @@
+using System;
+using System.IO;
+
+namespace Hazel
+{
+    ///
+    public class MessageWriter : IRecyclable
+    {
+        public static int BufferSize = 64000;
+        private static readonly ObjectPool<MessageWriter> objectPool = new ObjectPool<MessageWriter>(() => 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);
+        }
+
+        ///
+        /// <param name="sendOption">The option specifying how the message should be sent.</param>
+        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);
+        }
+    }
+}
index 2f5dfa12de904a3612ba078015c801ae209974df..4ea5c8826eb71ea7d873ed6eb826d3277a0f3882 100644 (file)
@@ -54,7 +54,7 @@ namespace Hazel.Udp
         }
 
         /// <inheritdoc />
-        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)
index 209eb8d61d246525e8728b90776ff142f3aaac0a..8de05ba93e1a073b4cdc3ce3b970b8a049bf96e9 100644 (file)
@@ -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);
             }
         }
 
index fd40398876f3cd837d0ed7028e950cb62a704216..1a1a3ffebe188149e61996b88a8aa9a58d54f730 100644 (file)
@@ -182,7 +182,7 @@ namespace Hazel.Udp
         /// <param name="buffer">The buffer to attach to.</param>
         /// <param name="offset">The offset to attach at.</param>
         /// <param name="ackCallback">The callback to make once the packet has been acknowledged.</param>
-        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
         /// <param name="byte2">The second identification byte.</param>
         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);
         }
     }
 }
index 7f4c22d4a8dc694f2e954037df62496b24847218..54d4c9912ec8184749a88b5fecaf1a15e773b8be 100644 (file)
@@ -27,7 +27,37 @@ namespace Hazel.Udp
         ///     Writes the given bytes to the connection.
         /// </summary>
         /// <param name="bytes">The bytes to write.</param>
-        protected abstract void WriteBytesToConnection(byte[] bytes);
+        protected abstract void WriteBytesToConnection(byte[] bytes, int length);
+
+        /// <inheritdoc/>
+        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;
+            }
+        }
 
         /// <inheritdoc/>
         /// <remarks>
@@ -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);
         }
index cb17c70e16c3cbc834b3ab74d93364e064a41e5a..22bdbbc7e2ca5d1c63da59819223be0c1f0913a6 100644 (file)
@@ -183,14 +183,14 @@ namespace Hazel.Udp
         /// </summary>
         /// <param name="bytes">The bytes to send.</param>
         /// <param name="endPoint">The endpoint to send to.</param>
-        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)
index 71bd8f6a8fd3d8b261fd9a4d45d5a97ed28d23f6..1f47e6f8e81c720acc93d7c43ed320bb7cf0e695 100644 (file)
@@ -45,7 +45,7 @@ namespace Hazel.Udp
         }
 
         /// <inheritdoc />
-        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);
         }
 
         /// <inheritdoc />