--- /dev/null
+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
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>
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="BinaryWriterExtensions.cs" />
<Compile Include="Connection.cs" />
<Compile Include="ConnectionEndPoint.cs" />
<Compile Include="ConnectionListener.cs" />
<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" />
--- /dev/null
+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);
+ }
+ }
+}
}
/// <inheritdoc />
- protected override void WriteBytesToConnection(byte[] bytes)
+ protected override void WriteBytesToConnection(byte[] bytes, int length)
{
lock (stateLock)
{
socket.BeginSendTo(
bytes,
0,
- bytes.Length,
+ length,
SocketFlags.None,
RemoteEndPoint,
delegate (IAsyncResult result)
}
//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);
}
}
/// <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)
try
{
- WriteBytesToConnection(p.Data);
+ WriteBytesToConnection(p.Data, sendLength);
}
catch (InvalidOperationException e)
{
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);
}
/// <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);
}
}
}
/// 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>
Buffer.BlockCopy(data, offset, bytes, bytes.Length - length, length);
//Write to connection
- WriteBytesToConnection(bytes);
+ WriteBytesToConnection(bytes, bytes.Length);
Statistics.LogUnreliableSend(length, bytes.Length);
}
/// </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)
}
/// <inheritdoc />
- protected override void WriteBytesToConnection(byte[] bytes)
+ protected override void WriteBytesToConnection(byte[] bytes, int length)
{
lock (stateLock)
{
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 />