From a3781b0b87ad9a1affb0d487df08df983ae652f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Wed, 14 Aug 2019 18:09:26 +0200 Subject: [PATCH 01/19] feat: Added fragmentation --- Ruffles/Channeling/IChannel.cs | 2 +- Ruffles/Configuration/SocketConfig.cs | 1 + Ruffles/Memory/MemoryManager.cs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Ruffles/Channeling/IChannel.cs b/Ruffles/Channeling/IChannel.cs index 0b40244..4f74922 100644 --- a/Ruffles/Channeling/IChannel.cs +++ b/Ruffles/Channeling/IChannel.cs @@ -7,7 +7,7 @@ internal interface IChannel { HeapMemory HandlePoll(); ArraySegment? HandleIncomingMessagePoll(ArraySegment payload, out bool hasMore); - HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dealloc); + HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc); void HandleAck(ArraySegment payload); void Reset(); void InternalUpdate(); diff --git a/Ruffles/Configuration/SocketConfig.cs b/Ruffles/Configuration/SocketConfig.cs index f1c24a4..1c0c995 100644 --- a/Ruffles/Configuration/SocketConfig.cs +++ b/Ruffles/Configuration/SocketConfig.cs @@ -50,6 +50,7 @@ public class SocketConfig /// The maximum delay before merged packets are sent. /// public ulong MaxMergeDelay = 250; + public ushort MaxMessageSize = 1450; // Memory /// diff --git a/Ruffles/Memory/MemoryManager.cs b/Ruffles/Memory/MemoryManager.cs index 8093dad..baf3b21 100644 --- a/Ruffles/Memory/MemoryManager.cs +++ b/Ruffles/Memory/MemoryManager.cs @@ -13,7 +13,7 @@ internal static class MemoryManager private const uint minBufferSize = 64; private const uint bufferMultiple = 64; - private static uint CalculateMultiple(uint minSize, uint multiple) + internal static uint CalculateMultiple(uint minSize, uint multiple) { uint remainder = minSize % multiple; From e259a40f705b1c1316cedf5e10b22a641583e157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Wed, 14 Aug 2019 18:26:07 +0200 Subject: [PATCH 02/19] Added fragmented channel --- .../ReliableSequencedFragmentedChannel .cs | 553 ++++++++++++++++++ 1 file changed, 553 insertions(+) create mode 100644 Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs diff --git a/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs b/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs new file mode 100644 index 0000000..6009e3b --- /dev/null +++ b/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs @@ -0,0 +1,553 @@ +using System; +using Ruffles.Configuration; +using Ruffles.Connections; +using Ruffles.Core; +using Ruffles.Memory; +using Ruffles.Messaging; + +namespace Ruffles.Channeling.Channels +{ + // TODO: Make single fragment messages faster + // TODO: Remove array allocs + internal class ReliableSequencedFragmentedChannel : IChannel + { + internal struct PendingOutgoingPacket : IMemoryReleasable + { + public bool IsAlloced + { + get + { + if (Fragments == null) + { + return false; + } + + for (int i = 0; i < Fragments.Length; i++) + { + if (Fragments[i].Alive && Fragments[i].IsAlloced) + { + return true; + } + } + + return false; + } + } + + public PendingOutgoingFragment[] Fragments; + public bool Alive; + + public void DeAlloc() + { + if (IsAlloced) + { + for (int i = 0; i < Fragments.Length; i++) + { + if (Fragments[i].Alive && Fragments[i].IsAlloced) + { + Fragments[i].DeAlloc(); + } + } + } + } + } + + + internal struct PendingOutgoingFragment : IMemoryReleasable + { + public bool IsAlloced => Memory != null && !Memory.isDead; + + public ushort Sequence; + public HeapMemory Memory; + public DateTime LastSent; + public DateTime FirstSent; + public ushort Attempts; + public bool Alive; + + public void DeAlloc() + { + if (IsAlloced) + { + MemoryManager.DeAlloc(Memory); + } + } + } + + internal struct PendingIncomingPacket : IMemoryReleasable + { + public bool IsAlloced + { + get + { + if (Fragments.Array == null) + { + return false; + } + + for (int i = 0; i < Fragments.Count; i++) + { + if (Fragments.Array[Fragments.Offset + i] != null && !Fragments.Array[Fragments.Offset + i].isDead) + { + return true; + } + } + + return false; + } + } + + public bool IsComplete + { + get + { + if (Fragments.Array == null || Size == null || Fragments.Count < Size) + { + return false; + } + + for (int i = 0; i < Fragments.Count; i++) + { + if (Fragments.Array[Fragments.Offset + i] == null || Fragments.Array[Fragments.Offset + i].isDead) + { + return false; + } + } + + return true; + } + } + + public uint TotalByteSize + { + get + { + uint byteSize = 0; + + if (!IsComplete) + { + // TODO: Throw + return byteSize; + } + + + for (int i = 0; i < Fragments.Count; i++) + { + byteSize += Fragments.Array[Fragments.Offset + i].VirtualCount; + } + + return byteSize; + } + } + + public ushort Sequence; + public ushort? Size; + public ArraySegment Fragments; + public bool Alive; + + public void DeAlloc() + { + if (IsAlloced) + { + for (int i = 0; i < Fragments.Count; i++) + { + if (Fragments.Array[Fragments.Offset + i] != null && !Fragments.Array[Fragments.Offset + i].isDead) + { + MemoryManager.DeAlloc(Fragments.Array[Fragments.Offset + i]); + } + } + } + } + } + + // Incoming sequencing + private ushort _incomingLowestAckedSequence; + private readonly HeapableSlidingWindow _receiveSequencer; + + // Outgoing sequencing + private ushort _lastOutboundSequenceNumber; + private readonly HeapableSlidingWindow _sendSequencer; + + // Channel info + private readonly byte channelId; + private readonly Connection connection; + private readonly RuffleSocket socket; + private readonly SocketConfig config; + + internal ReliableSequencedFragmentedChannel(byte channelId, Connection connection, RuffleSocket socket, SocketConfig config) + { + this.channelId = channelId; + this.connection = connection; + this.socket = socket; + this.config = config; + + // Alloc the in flight windows for receive and send + _receiveSequencer = new HeapableSlidingWindow(config.ReliabilityWindowSize, true, sizeof(ushort)); + _sendSequencer = new HeapableSlidingWindow(config.ReliabilityWindowSize, true, sizeof(ushort)); + } + + public HeapMemory HandlePoll() + { + // Get the next packet that is not yet given to the user. + PendingIncomingPacket nextPacket = _receiveSequencer[_incomingLowestAckedSequence + 1]; + + if (nextPacket.Alive && nextPacket.IsComplete) + { + ++_incomingLowestAckedSequence; + + // Get the total size of all fragments + uint totalSize = nextPacket.TotalByteSize; + + // Alloc memory for that large segment + HeapMemory memory = MemoryManager.Alloc(totalSize); + + // Keep track of where we are, fragments COULD have different sizes. + int bufferPosition = 0; + + // Copy all the parts + for (int i = 0; i < nextPacket.Fragments.Count; i++) + { + // Copy fragment to final buffer + Buffer.BlockCopy(nextPacket.Fragments.Array[nextPacket.Fragments.Offset + i].Buffer, (int)nextPacket.Fragments.Array[nextPacket.Fragments.Offset + i].VirtualOffset, memory.Buffer, bufferPosition, (int)nextPacket.Fragments.Array[nextPacket.Fragments.Offset + i].VirtualCount); + + bufferPosition += (int)nextPacket.Fragments.Array[nextPacket.Fragments.Offset + i].VirtualCount; + } + + // Free the memory of all the individual fragments + nextPacket.DeAlloc(); + + // Kill + _receiveSequencer[_incomingLowestAckedSequence] = new PendingIncomingPacket() + { + Alive = false, + Sequence = 0, + Size = null, + Fragments = new ArraySegment() + }; + + + // HandlePoll gives the memory straight to the user, they are responsible for deallocing to prevent leaks + return memory; + } + + return null; + } + + public ArraySegment? HandleIncomingMessagePoll(ArraySegment payload, out bool hasMore) + { + // Read the sequence number + ushort sequence = (ushort)(payload.Array[payload.Offset] | (ushort)(payload.Array[payload.Offset + 1] << 8)); + // Read the raw fragment data + ushort encodedFragment = (ushort)(payload.Array[payload.Offset + 2] | (ushort)(payload.Array[payload.Offset + 3] << 8)); + // The fragmentId is the last 15 least significant bits + ushort fragment = (ushort)(encodedFragment & 32767); + // IsFinal is the most significant bit + bool isFinal = (ushort)((encodedFragment & 32768) >> 15) == 1; + + if (SequencingUtils.Distance(sequence, _incomingLowestAckedSequence, sizeof(ushort)) <= 0 || + (_receiveSequencer[sequence].Alive && _receiveSequencer[sequence].IsComplete) || + (_receiveSequencer[sequence].Alive && _receiveSequencer[sequence].Fragments.Array != null && _receiveSequencer[sequence].Fragments.Count > fragment && _receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment] != null && !_receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment].isDead)) + { + // We have already acked this message. Ack again + + SendAckEncoded(sequence, encodedFragment); + + hasMore = false; + return null; + } + else + { + // This is a packet after the last. One that is not yet completed + + // If this is the first fragment we ever get, index the data. + if (!_receiveSequencer[sequence].Alive) + { + _receiveSequencer[sequence] = new PendingIncomingPacket() + { + Alive = true, + // TODO: Remove hardcoded values + Fragments = new ArraySegment(new HeapMemory[isFinal ? fragment + 1 : 128], 0, fragment + 1), + Sequence = sequence, + Size = isFinal ? (ushort?)(fragment + 1) : null + }; + } + else + { + // If the first fragment we got was fragment 1 / 500. The fragments array will only be of size 128. We need to potentially resize it + if (_receiveSequencer[sequence].Fragments.Array.Length - _receiveSequencer[sequence].Fragments.Offset <= fragment) + { + // We need to expand the fragments array. + + // Calculate the target size of the array + // TODO: Remove hardcoded values + uint allocSize = Math.Max(128, MemoryManager.CalculateMultiple((uint)fragment + 1, 64)); + + // Alloc new array + ArraySegment newFragments = new ArraySegment(new HeapMemory[allocSize], _receiveSequencer[sequence].Fragments.Offset, fragment + 1); + + // Copy old values + Array.Copy(_receiveSequencer[sequence].Fragments.Array, newFragments.Array, _receiveSequencer[sequence].Fragments.Array.Length); + + // Update the index + _receiveSequencer[sequence] = new PendingIncomingPacket() + { + Fragments = newFragments, + Alive = _receiveSequencer[sequence].Alive, + Sequence = _receiveSequencer[sequence].Sequence, + Size = _receiveSequencer[sequence].Size + }; + } + + // We might also have to expand the virtual count + if (_receiveSequencer[sequence].Fragments.Count <= fragment) + { + // Update the virtual elngth of the array + _receiveSequencer[sequence] = new PendingIncomingPacket() + { + Fragments = new ArraySegment(_receiveSequencer[sequence].Fragments.Array, _receiveSequencer[sequence].Fragments.Offset, fragment + 1), + Alive = _receiveSequencer[sequence].Alive, + Sequence = _receiveSequencer[sequence].Sequence, + Size = _receiveSequencer[sequence].Size + }; + } + } + + + { + // Alloc some memory for the fragment + HeapMemory memory = MemoryManager.Alloc((uint)payload.Count - 4); + + // Copy the payload + Buffer.BlockCopy(payload.Array, payload.Offset + 4, memory.Buffer, 0, payload.Count - 4); + + // Add fragment to index + _receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment] = memory; + } + + // Send ack + SendAckEncoded(sequence, encodedFragment); + + // Sequenced never returns the original memory. Thus we need to return null and tell the caller to Poll instead. + hasMore = _receiveSequencer[_incomingLowestAckedSequence + 1].Alive && _receiveSequencer[_incomingLowestAckedSequence + 1].IsComplete; + + return null; + } + } + + public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) + { + // Increment the sequence number + _lastOutboundSequenceNumber++; + + // Calculate the amount of fragments required + int fragmentsRequired = (payload.Count + (config.MaxMessageSize - 1)) / config.MaxMessageSize; + + // Alloc array + HeapMemory[] fragments = new HeapMemory[fragmentsRequired]; + + int position = 0; + + for (ushort i = 0; i < fragments.Length; i++) + { + // Calculate message size + int messageSize = Math.Min(config.MaxMessageSize, payload.Count - position); + + // Allocate memory for each fragment + fragments[i] = MemoryManager.Alloc(((uint)Math.Min(config.MaxMessageSize, payload.Count - position)) + 6); + + // Write headers + fragments[i].Buffer[0] = HeaderPacker.Pack((byte)MessageType.Data, false); + fragments[i].Buffer[1] = channelId; + + // Write the sequence + fragments[i].Buffer[2] = (byte)_lastOutboundSequenceNumber; + fragments[i].Buffer[3] = (byte)(_lastOutboundSequenceNumber >> 8); + + // Write the fragment + fragments[i].Buffer[4] = (byte)(i & 32767); + fragments[i].Buffer[5] = (byte)(((i & 32767) >> 8) | (byte)(i == fragments.Length - 1 ? 128 : 0)); + + // Write the payload + Buffer.BlockCopy(payload.Array, payload.Offset + position, fragments[i].Buffer, (int)fragments[i].VirtualOffset, messageSize); + + // Increase the position + position += messageSize; + } + + // Tell the caller NOT to dealloc the memory, the channel needs it for resend purposes. + dealloc = false; + + // Alloc outgoing fragment structs + PendingOutgoingFragment[] outgoingFragments = new PendingOutgoingFragment[fragments.Length]; + + for (int i = 0; i < outgoingFragments.Length; i++) + { + // Add the memory to the outgoing sequencer + outgoingFragments[i] = new PendingOutgoingFragment() + { + Alive = true, + Attempts = 1, + LastSent = DateTime.Now, + FirstSent = DateTime.Now, + Sequence = _lastOutboundSequenceNumber, + Memory = fragments[i] + }; + } + + return fragments; + } + + public void HandleAck(ArraySegment payload) + { + // Read the sequence number + ushort sequence = (ushort)(payload.Array[payload.Offset] | (ushort)(payload.Array[payload.Offset + 1] << 8)); + // Read the raw fragment data + ushort encodedFragment = (ushort)(payload.Array[payload.Offset + 2] | (ushort)(payload.Array[payload.Offset + 3] << 8)); + // The fragmentId is the last 15 least significant bits + ushort fragment = (ushort)(encodedFragment & 32767); + // IsFinal is the most significant bit + bool isFinal = (ushort)((encodedFragment & 32768) >> 15) == 1; + + if (_sendSequencer[sequence].Alive && _sendSequencer[sequence].Fragments.Length > fragment && _sendSequencer[sequence].Fragments[fragment].Alive) + { + // Dealloc the memory held by the sequencer for the packet + _sendSequencer[sequence].Fragments[fragment].DeAlloc(); + + // TODO: Remove roundtripping from channeled packets and make specific ping-pong packets + + // Get the roundtrp + ulong roundtrip = (ulong)Math.Round((DateTime.Now - _sendSequencer[sequence].Fragments[fragment].FirstSent).TotalMilliseconds); + + // Report to the connection + connection.AddRoundtripSample(roundtrip); + + // Kill the fragment packet + _sendSequencer[sequence].Fragments[fragment] = new PendingOutgoingFragment() + { + Alive = false, + Sequence = sequence + }; + + bool hasAllocatedAndAliveFragments = false; + for (int i = 0; i < _sendSequencer[sequence].Fragments.Length; i++) + { + if (_sendSequencer[sequence].Fragments[i].Alive) + { + hasAllocatedAndAliveFragments = true; + break; + } + } + + if (!hasAllocatedAndAliveFragments) + { + // Dealloc the wrapper packet + _sendSequencer[sequence].DeAlloc(); + + // Kill the wrapper packet + _sendSequencer[sequence] = new PendingOutgoingPacket() + { + Alive = false + }; + } + } + + for (ushort i = sequence; _sendSequencer[i].Alive; i++) + { + _incomingLowestAckedSequence = i; + } + } + + public void Reset() + { + // Clear all incoming states + _receiveSequencer.Release(); + _incomingLowestAckedSequence = 0; + + // Clear all outgoing states + _sendSequencer.Release(); + _lastOutboundSequenceNumber = 0; + } + + private void SendAck(ushort sequence, ushort fragment, bool isFinal) + { + // Alloc ack memory + HeapMemory ackMemory = MemoryManager.Alloc(4); + + // Write header + ackMemory.Buffer[0] = HeaderPacker.Pack((byte)MessageType.Ack, false); + ackMemory.Buffer[1] = (byte)channelId; + + // Write sequence + ackMemory.Buffer[2] = (byte)sequence; + ackMemory.Buffer[3] = (byte)(sequence >> 8); + + // Write fragment + ackMemory.Buffer[4] = (byte)(fragment & 32767); + ackMemory.Buffer[5] = (byte)(((byte)((fragment & 32767) >> 8)) | (byte)(isFinal ? 128 : 0)); + + // Send ack + connection.SendRaw(new ArraySegment(ackMemory.Buffer, 0, 6), false); + + // Return memory + MemoryManager.DeAlloc(ackMemory); + } + + private void SendAckEncoded(ushort sequence, ushort encodedFragment) + { + // Alloc ack memory + HeapMemory ackMemory = MemoryManager.Alloc(4); + + // Write header + ackMemory.Buffer[0] = HeaderPacker.Pack((byte)MessageType.Ack, false); + ackMemory.Buffer[1] = (byte)channelId; + + // Write sequence + ackMemory.Buffer[2] = (byte)sequence; + ackMemory.Buffer[3] = (byte)(sequence >> 8); + + // Write fragment + ackMemory.Buffer[4] = (byte)encodedFragment; + ackMemory.Buffer[5] = (byte)(encodedFragment >> 8); + + // Send ack + connection.SendRaw(new ArraySegment(ackMemory.Buffer, 0, 6), false); + + // Return memory + MemoryManager.DeAlloc(ackMemory); + } + + public void InternalUpdate() + { + long distance = SequencingUtils.Distance(_lastOutboundSequenceNumber, _incomingLowestAckedSequence, sizeof(ushort)); + + for (ushort i = _incomingLowestAckedSequence; i < _incomingLowestAckedSequence + distance; i++) + { + if (_sendSequencer[i].Alive) + { + for (int j = 0; j < _sendSequencer[i].Fragments.Length; j++) + { + if (_sendSequencer[i].Fragments[j].Attempts > config.ReliabilityMaxResendAttempts) + { + // If they don't ack the message, disconnect them + connection.Disconnect(false); + } + else if ((DateTime.Now - _sendSequencer[i].Fragments[j].LastSent).TotalMilliseconds > connection.Roundtrip * config.ReliabilityResendRoundtripMultiplier) + { + _sendSequencer[i].Fragments[j] = new PendingOutgoingFragment() + { + Alive = true, + Attempts = (ushort)(_sendSequencer[i].Fragments[j].Attempts + 1), + LastSent = DateTime.Now, + FirstSent = _sendSequencer[i].Fragments[j].FirstSent, + Memory = _sendSequencer[i].Fragments[j].Memory, + Sequence = i + }; + + connection.SendRaw(new ArraySegment(_sendSequencer[i].Fragments[j].Memory.Buffer, (int)_sendSequencer[i].Fragments[j].Memory.VirtualOffset, (int)_sendSequencer[i].Fragments[j].Memory.VirtualCount), false); + } + } + } + } + } + } +} + \ No newline at end of file From 7106e8132d378240ffdd317fc0c48d649d6eed4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Mon, 2 Sep 2019 14:38:57 +0200 Subject: [PATCH 03/19] fix: Fixed various fragmentation issues --- Ruffles.Example/Program.cs | 37 +++++++++--- Ruffles/Channeling/ChannelType.cs | 3 +- .../Channeling/Channels/ReliableChannel.cs | 9 ++- .../Channels/ReliableSequencedChannel.cs | 9 ++- .../ReliableSequencedFragmentedChannel .cs | 58 ++++++++++++------- .../Channeling/Channels/UnreliableChannel.cs | 9 ++- .../Channels/UnreliableRawChannel.cs | 9 ++- .../Channels/UnreliableSequencedChannel.cs | 9 ++- Ruffles/Configuration/SocketConfig.cs | 8 +-- Ruffles/Core/RuffleSocket.cs | 39 +++++++++---- Ruffles/Memory/MemoryManager.cs | 24 +++++--- Ruffles/Messaging/PacketHandler.cs | 12 +++- Ruffles/Utils/Logging.cs | 2 +- 13 files changed, 160 insertions(+), 68 deletions(-) diff --git a/Ruffles.Example/Program.cs b/Ruffles.Example/Program.cs index 19003e6..851160f 100644 --- a/Ruffles.Example/Program.cs +++ b/Ruffles.Example/Program.cs @@ -17,15 +17,30 @@ public class Program ChannelType.Reliable, ChannelType.ReliableSequenced, ChannelType.Unreliable, - ChannelType.UnreliableSequenced + ChannelType.UnreliableSequenced, + ChannelType.ReliableSequencedFragmented }, - DualListenPort = 5674 + DualListenPort = 5674, + SimulatorConfig = new Simulation.SimulatorConfig() + { + DropPercentage = 0.1f, + MaxLatency = 1000, + MinLatency = 300 + }, + UseSimulator = true }; internal static readonly SocketConfig ClientConfig = new SocketConfig() { ChallengeDifficulty = 25, // Difficulty 25 is fairly hard - DualListenPort = 0 // Port 0 means we get a port by the operating system + DualListenPort = 0, // Port 0 means we get a port by the operating system + SimulatorConfig = new Simulation.SimulatorConfig() + { + DropPercentage = 0.1f, + MaxLatency = 1000, + MinLatency = 300 + }, + UseSimulator = true }; @@ -112,8 +127,11 @@ private static void NoRufflesManager() if (clientEvent.Type == NetworkEventType.Data) { + Console.WriteLine("IsCorrect: " + (clientEvent.Data.Count == (1024 + 1024 * (messagesReceived % 5))) + ", Expected: " + (1024 + 1024 * (messagesReceived % 5)) + ", got: " + clientEvent.Data.Count); + messagesReceived++; - Console.WriteLine("Got message: \"" + Encoding.ASCII.GetString(clientEvent.Data.Array, clientEvent.Data.Offset, clientEvent.Data.Count) + "\""); + //Console.WriteLine("Got message of size: " + clientEvent.Data.Count); + //Console.WriteLine("Got message: \"" + Encoding.ASCII.GetString(clientEvent.Data.Array, clientEvent.Data.Offset, clientEvent.Data.Count) + "\""); clientEvent.Recycle(); } @@ -124,12 +142,17 @@ private static void NoRufflesManager() } if ((DateTime.Now - started).TotalSeconds > 10 && (DateTime.Now - lastSent).TotalSeconds >= 1) - { + { + Console.WriteLine("Sending size: " + (1024 + (1024 * (messageCounter % 5)))); + byte[] largeFragment = new byte[1024 + 1024 * (messageCounter % 5)]; + + byte[] helloReliable = Encoding.ASCII.GetBytes("This message was sent over a reliable channel" + messageCounter); byte[] helloReliableSequenced = Encoding.ASCII.GetBytes("This message was sent over a reliable sequenced channel" + messageCounter); - server.Send(new ArraySegment(helloReliableSequenced, 0, helloReliableSequenced.Length), clientId, 0, false); - server.Send(new ArraySegment(helloReliable, 0, helloReliable.Length), clientId, 1, false); + //server.Send(new ArraySegment(helloReliableSequenced, 0, helloReliableSequenced.Length), clientId, 0, false); + //server.Send(new ArraySegment(helloReliable, 0, helloReliable.Length), clientId, 1, false); + server.Send(new ArraySegment(largeFragment, 0, largeFragment.Length), clientId, 4, false); messageCounter++; lastSent = DateTime.Now; diff --git a/Ruffles/Channeling/ChannelType.cs b/Ruffles/Channeling/ChannelType.cs index feb791a..56a0333 100644 --- a/Ruffles/Channeling/ChannelType.cs +++ b/Ruffles/Channeling/ChannelType.cs @@ -29,6 +29,7 @@ public enum ChannelType : byte /// Messages are not guaranteed to be delivered, the order is not guaranteed. /// Duplicate packets are not dropped. /// - UnreliableRaw + UnreliableRaw, + ReliableSequencedFragmented } } diff --git a/Ruffles/Channeling/Channels/ReliableChannel.cs b/Ruffles/Channeling/Channels/ReliableChannel.cs index 34d1bae..6b93f42 100644 --- a/Ruffles/Channeling/Channels/ReliableChannel.cs +++ b/Ruffles/Channeling/Channels/ReliableChannel.cs @@ -105,7 +105,9 @@ public HeapMemory HandlePoll() return null; } - public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dealloc) + private readonly HeapMemory[] SINGLE_MESSAGE_ARRAY = new HeapMemory[1]; + + public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { // Increment the sequence number _lastOutboundSequenceNumber++; @@ -138,7 +140,10 @@ public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dea // Tell the caller NOT to dealloc the memory, the channel needs it for resend purposes. dealloc = false; - return memory; + // Assign memory + SINGLE_MESSAGE_ARRAY[0] = memory; + + return SINGLE_MESSAGE_ARRAY; } public void HandleAck(ArraySegment payload) diff --git a/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs b/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs index 7b2b418..e0590d6 100644 --- a/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs +++ b/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs @@ -147,7 +147,9 @@ public HeapMemory HandlePoll() return null; } - public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dealloc) + private readonly HeapMemory[] SINGLE_MESSAGE_ARRAY = new HeapMemory[1]; + + public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { // Increment the sequence number _lastOutboundSequenceNumber++; @@ -180,7 +182,10 @@ public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dea // Tell the caller NOT to dealloc the memory, the channel needs it for resend purposes. dealloc = false; - return memory; + // Assign memory + SINGLE_MESSAGE_ARRAY[0] = memory; + + return SINGLE_MESSAGE_ARRAY; } public void HandleAck(ArraySegment payload) diff --git a/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs b/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs index 6009e3b..545d45c 100644 --- a/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs +++ b/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs @@ -252,6 +252,7 @@ public HeapMemory HandlePoll() SendAckEncoded(sequence, encodedFragment); hasMore = false; + return null; } else @@ -293,7 +294,7 @@ public HeapMemory HandlePoll() Fragments = newFragments, Alive = _receiveSequencer[sequence].Alive, Sequence = _receiveSequencer[sequence].Sequence, - Size = _receiveSequencer[sequence].Size + Size = isFinal ? (ushort?)(fragment + 1) : _receiveSequencer[sequence].Size }; } @@ -306,12 +307,12 @@ public HeapMemory HandlePoll() Fragments = new ArraySegment(_receiveSequencer[sequence].Fragments.Array, _receiveSequencer[sequence].Fragments.Offset, fragment + 1), Alive = _receiveSequencer[sequence].Alive, Sequence = _receiveSequencer[sequence].Sequence, - Size = _receiveSequencer[sequence].Size + Size = isFinal ? (ushort?)(fragment + 1) : _receiveSequencer[sequence].Size }; } } - + if (_receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment] == null || _receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment].isDead) { // Alloc some memory for the fragment HeapMemory memory = MemoryManager.Alloc((uint)payload.Count - 4); @@ -352,7 +353,7 @@ public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool d int messageSize = Math.Min(config.MaxMessageSize, payload.Count - position); // Allocate memory for each fragment - fragments[i] = MemoryManager.Alloc(((uint)Math.Min(config.MaxMessageSize, payload.Count - position)) + 6); + fragments[i] = MemoryManager.Alloc((uint)(messageSize + 6)); // Write headers fragments[i].Buffer[0] = HeaderPacker.Pack((byte)MessageType.Data, false); @@ -367,7 +368,7 @@ public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool d fragments[i].Buffer[5] = (byte)(((i & 32767) >> 8) | (byte)(i == fragments.Length - 1 ? 128 : 0)); // Write the payload - Buffer.BlockCopy(payload.Array, payload.Offset + position, fragments[i].Buffer, (int)fragments[i].VirtualOffset, messageSize); + Buffer.BlockCopy(payload.Array, payload.Offset + position, fragments[i].Buffer, 6, messageSize); // Increase the position position += messageSize; @@ -381,7 +382,7 @@ public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool d for (int i = 0; i < outgoingFragments.Length; i++) { - // Add the memory to the outgoing sequencer + // Add the memory to the outgoing sequencer array outgoingFragments[i] = new PendingOutgoingFragment() { Alive = true, @@ -393,6 +394,13 @@ public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool d }; } + // Add the memory to the outgoing sequencer + _sendSequencer[_lastOutboundSequenceNumber] = new PendingOutgoingPacket() + { + Alive = true, + Fragments = outgoingFragments + }; + return fragments; } @@ -525,24 +533,30 @@ public void InternalUpdate() { for (int j = 0; j < _sendSequencer[i].Fragments.Length; j++) { - if (_sendSequencer[i].Fragments[j].Attempts > config.ReliabilityMaxResendAttempts) + if (_sendSequencer[i].Fragments[j].Alive) { - // If they don't ack the message, disconnect them - connection.Disconnect(false); - } - else if ((DateTime.Now - _sendSequencer[i].Fragments[j].LastSent).TotalMilliseconds > connection.Roundtrip * config.ReliabilityResendRoundtripMultiplier) - { - _sendSequencer[i].Fragments[j] = new PendingOutgoingFragment() + if (_sendSequencer[i].Fragments[j].Attempts > config.ReliabilityMaxResendAttempts) + { + // If they don't ack the message, disconnect them + connection.Disconnect(false); + + return; + } + else if ((DateTime.Now - _sendSequencer[i].Fragments[j].LastSent).TotalMilliseconds > connection.Roundtrip * config.ReliabilityResendRoundtripMultiplier) { - Alive = true, - Attempts = (ushort)(_sendSequencer[i].Fragments[j].Attempts + 1), - LastSent = DateTime.Now, - FirstSent = _sendSequencer[i].Fragments[j].FirstSent, - Memory = _sendSequencer[i].Fragments[j].Memory, - Sequence = i - }; - - connection.SendRaw(new ArraySegment(_sendSequencer[i].Fragments[j].Memory.Buffer, (int)_sendSequencer[i].Fragments[j].Memory.VirtualOffset, (int)_sendSequencer[i].Fragments[j].Memory.VirtualCount), false); + _sendSequencer[i].Fragments[j] = new PendingOutgoingFragment() + { + Alive = true, + Attempts = (ushort)(_sendSequencer[i].Fragments[j].Attempts + 1), + LastSent = DateTime.Now, + FirstSent = _sendSequencer[i].Fragments[j].FirstSent, + Memory = _sendSequencer[i].Fragments[j].Memory, + Sequence = i + }; + + Console.WriteLine("Resending fragment for sequence: " + i + " and fragment: " + j); + connection.SendRaw(new ArraySegment(_sendSequencer[i].Fragments[j].Memory.Buffer, (int)_sendSequencer[i].Fragments[j].Memory.VirtualOffset, (int)_sendSequencer[i].Fragments[j].Memory.VirtualCount), false); + } } } } diff --git a/Ruffles/Channeling/Channels/UnreliableChannel.cs b/Ruffles/Channeling/Channels/UnreliableChannel.cs index 19eba67..6d2604c 100644 --- a/Ruffles/Channeling/Channels/UnreliableChannel.cs +++ b/Ruffles/Channeling/Channels/UnreliableChannel.cs @@ -28,7 +28,9 @@ internal UnreliableChannel(byte channelId, Connection connection, SocketConfig c _incomingAckedPackets = new SlidingWindow(config.ReliabilityWindowSize, true, sizeof(ushort)); } - public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dealloc) + private readonly HeapMemory[] SINGLE_MESSAGE_ARRAY = new HeapMemory[1]; + + public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { // Increment the sequence number _lastOutboundSequenceNumber++; @@ -50,7 +52,10 @@ public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dea // Tell the caller to deallc the memory dealloc = true; - return memory; + // Assign memory + SINGLE_MESSAGE_ARRAY[0] = memory; + + return SINGLE_MESSAGE_ARRAY; } public void HandleAck(ArraySegment payload) diff --git a/Ruffles/Channeling/Channels/UnreliableRawChannel.cs b/Ruffles/Channeling/Channels/UnreliableRawChannel.cs index 39494b6..df3b886 100644 --- a/Ruffles/Channeling/Channels/UnreliableRawChannel.cs +++ b/Ruffles/Channeling/Channels/UnreliableRawChannel.cs @@ -20,7 +20,9 @@ internal UnreliableRawChannel(byte channelId, Connection connection, SocketConfi this.config = config; } - public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dealloc) + private readonly HeapMemory[] SINGLE_MESSAGE_ARRAY = new HeapMemory[1]; + + public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { // Allocate the memory HeapMemory memory = MemoryManager.Alloc((uint)payload.Count + 2); @@ -35,7 +37,10 @@ public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dea // Tell the caller to deallc the memory dealloc = true; - return memory; + // Assign memory + SINGLE_MESSAGE_ARRAY[0] = memory; + + return SINGLE_MESSAGE_ARRAY; } public void HandleAck(ArraySegment payload) diff --git a/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs b/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs index 8296d1e..8c36179 100644 --- a/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs +++ b/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs @@ -23,7 +23,9 @@ internal UnreliableSequencedChannel(byte channelId, Connection connection) this.connection = connection; } - public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dealloc) + private readonly HeapMemory[] SINGLE_MESSAGE_ARRAY = new HeapMemory[1]; + + public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { // Increment the sequence number _lastOutboundSequenceNumber++; @@ -45,7 +47,10 @@ public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dea // Tell the caller to deallc the memory dealloc = true; - return memory; + // Assign memory + SINGLE_MESSAGE_ARRAY[0] = memory; + + return SINGLE_MESSAGE_ARRAY; } internal HeapMemory CreateOutgoingHeartbeatMessage() diff --git a/Ruffles/Configuration/SocketConfig.cs b/Ruffles/Configuration/SocketConfig.cs index 1c0c995..bd90497 100644 --- a/Ruffles/Configuration/SocketConfig.cs +++ b/Ruffles/Configuration/SocketConfig.cs @@ -50,7 +50,7 @@ public class SocketConfig /// The maximum delay before merged packets are sent. /// public ulong MaxMergeDelay = 250; - public ushort MaxMessageSize = 1450; + public ushort MaxMessageSize = 1400; // Memory /// @@ -66,15 +66,15 @@ public class SocketConfig /// /// The amount of milliseconds from the connection request that the connection has to solve the challenge and complete the connection handshake. /// - public ulong HandshakeTimeout = 10000; + public ulong HandshakeTimeout = 30_000; /// /// The amount of milliseconds of packet silence before a already connected connection will be disconnected. /// - public ulong ConnectionTimeout = 10000; + public ulong ConnectionTimeout = 30_000; /// /// The amount milliseconds between heartbeat keep-alive packets are sent. /// - public ulong HeartbeatDelay = 2000; + public ulong HeartbeatDelay = 20_000; // Handshake resends /// diff --git a/Ruffles/Core/RuffleSocket.cs b/Ruffles/Core/RuffleSocket.cs index 3fcdc48..14651f0 100644 --- a/Ruffles/Core/RuffleSocket.cs +++ b/Ruffles/Core/RuffleSocket.cs @@ -256,7 +256,7 @@ public Connection Connect(EndPoint endpoint) for (byte i = 0; i < sizeof(ulong); i++) outgoingInternalBuffer[1 + sizeof(ulong) + i] = ((byte)(counter >> (i * 8))); // Write IV - for (byte i = 0; i < sizeof(ulong); i++) outgoingInternalBuffer[1 + sizeof(ulong) * 2 + i] = ((byte)(iv >> (i * 8))); + for (byte i = 0; i < sizeof(ulong); i++) outgoingInternalBuffer[1 + (sizeof(ulong) * 2) + i] = ((byte)(iv >> (i * 8))); } int minSize = 1 + (config.TimeBasedConnectionChallenge ? sizeof(ulong) * 3 : 0); @@ -405,13 +405,13 @@ private void CheckConnectionResends() if (config.TimeBasedConnectionChallenge) { // Write the response unix time - for (byte x = 0; x < sizeof(ulong); x++) outgoingInternalBuffer[1 + x] = ((byte)(Connections[i].PreConnectionChallengeTimestamp >> (i * 8))); + for (byte x = 0; x < sizeof(ulong); x++) outgoingInternalBuffer[1 + x] = ((byte)(Connections[i].PreConnectionChallengeTimestamp >> (x * 8))); // Write counter - for (byte x = 0; x < sizeof(ulong); x++) outgoingInternalBuffer[1 + sizeof(ulong) + x] = ((byte)(Connections[i].PreConnectionChallengeCounter >> (i * 8))); + for (byte x = 0; x < sizeof(ulong); x++) outgoingInternalBuffer[1 + sizeof(ulong) + x] = ((byte)(Connections[i].PreConnectionChallengeCounter >> (x * 8))); // Write IV - for (byte x = 0; x < sizeof(ulong); x++) outgoingInternalBuffer[1 + sizeof(ulong) * 2 + x] = ((byte)(Connections[i].PreConnectionChallengeIV >> (i * 8))); + for (byte x = 0; x < sizeof(ulong); x++) outgoingInternalBuffer[1 + (sizeof(ulong) * 2) + x] = ((byte)(Connections[i].PreConnectionChallengeIV >> (x * 8))); } int minSize = 1 + (config.TimeBasedConnectionChallenge ? sizeof(ulong) * 3 : 0); @@ -688,14 +688,14 @@ internal void HandlePacket(ArraySegment payload, EndPoint endpoint) ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) + 7] << 56)); // Read the initialization vector they used - ulong userIv = (((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2]) | - ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2 + 1] << 8) | - ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2 + 2] << 16) | - ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2 + 3] << 24) | - ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2 + 4] << 32) | - ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2 + 5] << 40) | - ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2 + 6] << 48) | - ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2 + 7] << 56)); + ulong userIv = (((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2)]) | + ((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2) + 1] << 8) | + ((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2) + 2] << 16) | + ((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2) + 3] << 24) | + ((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2) + 4] << 32) | + ((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2) + 5] << 40) | + ((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2) + 6] << 48) | + ((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2) + 7] << 56)); // Ensure they dont reuse a IV if (challengeInitializationVectors[userIv]) @@ -971,6 +971,11 @@ internal void HandlePacket(ArraySegment payload, EndPoint endpoint) pendingConnection.ChannelTypes[i] = ChannelType.UnreliableRaw; } break; + case (byte)ChannelType.ReliableSequencedFragmented: + { + pendingConnection.ChannelTypes[i] = ChannelType.ReliableSequencedFragmented; + } + break; default: { // Unknown channel type. Disconnect. @@ -1026,6 +1031,11 @@ internal void HandlePacket(ArraySegment payload, EndPoint endpoint) pendingConnection.Channels[i] = new UnreliableRawChannel(i, pendingConnection, config); } break; + case ChannelType.ReliableSequencedFragmented: + { + pendingConnection.Channels[i] = new ReliableSequencedFragmentedChannel(i, pendingConnection, this, config); + } + break; default: { // Unknown channel type. Disconnect. @@ -1348,6 +1358,11 @@ internal Connection AddNewConnection(EndPoint endpoint, ConnectionState state) connection.Channels[x] = new UnreliableRawChannel(x, connection, config); } break; + case ChannelType.ReliableSequencedFragmented: + { + connection.Channels[x] = new ReliableSequencedFragmentedChannel(x, connection, this, config); + } + break; default: { // Unknown channel type. Disconnect. diff --git a/Ruffles/Memory/MemoryManager.cs b/Ruffles/Memory/MemoryManager.cs index baf3b21..abe0d1e 100644 --- a/Ruffles/Memory/MemoryManager.cs +++ b/Ruffles/Memory/MemoryManager.cs @@ -42,18 +42,26 @@ internal static HeapMemory Alloc(uint size) _hasWarnedAboutLeak = true; } - return new HeapMemory(allocSize); - } + HeapMemory memory = new HeapMemory(allocSize); - HeapMemory memory = _pooledMemory.Dequeue(); + memory.isDead = false; + memory.VirtualCount = size; + memory.VirtualOffset = 0; + + return memory; + } + else + { + HeapMemory memory = _pooledMemory.Dequeue(); - memory.EnsureSize(allocSize); + memory.EnsureSize(allocSize); - memory.isDead = false; - memory.VirtualCount = allocSize; - memory.VirtualOffset = 0; + memory.isDead = false; + memory.VirtualCount = size; + memory.VirtualOffset = 0; - return memory; + return memory; + } } internal static void DeAlloc(HeapMemory memory) diff --git a/Ruffles/Messaging/PacketHandler.cs b/Ruffles/Messaging/PacketHandler.cs index b80e826..05c5c14 100644 --- a/Ruffles/Messaging/PacketHandler.cs +++ b/Ruffles/Messaging/PacketHandler.cs @@ -86,17 +86,23 @@ internal static void SendMessage(ArraySegment payload, Connection connecti IChannel channel = connection.Channels[channelId]; - HeapMemory messageMemory = channel.CreateOutgoingMessage(payload, out bool dealloc); + HeapMemory[] messageMemory = channel.CreateOutgoingMessage(payload, out bool dealloc); if (messageMemory != null) { - connection.SendRaw(new ArraySegment(messageMemory.Buffer, (int)messageMemory.VirtualOffset, (int)messageMemory.VirtualCount), noDelay); + for (int i = 0; i < messageMemory.Length; i++) + { + connection.SendRaw(new ArraySegment(messageMemory[i].Buffer, (int)messageMemory[i].VirtualOffset, (int)messageMemory[i].VirtualCount), noDelay); + } } if (dealloc) { // DeAlloc the memory again. This is done for unreliable channels that need the message after the initial send. - MemoryManager.DeAlloc(messageMemory); + for (int i = 0; i < messageMemory.Length; i++) + { + MemoryManager.DeAlloc(messageMemory[i]); + } } } } diff --git a/Ruffles/Utils/Logging.cs b/Ruffles/Utils/Logging.cs index 33e8a2d..6d71e13 100644 --- a/Ruffles/Utils/Logging.cs +++ b/Ruffles/Utils/Logging.cs @@ -10,7 +10,7 @@ public static class Logging /// /// Occurs when ruffles spits out an info log. /// - public static event Action OnInfoLog = (value) => Console.WriteLine("[INFO] " + value); + public static event Action OnInfoLog;//Console.WriteLine("[INFO] " + value); /// /// Occurs when ruffles spits out a warning log. /// From 4d80340ca333c30ebc4c1fd81c70b150b4835629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Mon, 2 Sep 2019 14:50:36 +0200 Subject: [PATCH 04/19] Finalized ReliableSequencedFragmented channel --- Ruffles/Channeling/ChannelType.cs | 3 +- .../Channeling/Channels/ReliableChannel.cs | 9 ++- .../Channels/ReliableSequencedChannel.cs | 9 ++- .../ReliableSequencedFragmentedChannel .cs | 58 ++++++++++++------- .../Channeling/Channels/UnreliableChannel.cs | 9 ++- .../Channels/UnreliableRawChannel.cs | 9 ++- .../Channels/UnreliableSequencedChannel.cs | 9 ++- Ruffles/Core/RuffleSocket.cs | 39 +++++++++---- Ruffles/Memory/MemoryManager.cs | 24 +++++--- Ruffles/Messaging/PacketHandler.cs | 12 +++- 10 files changed, 125 insertions(+), 56 deletions(-) diff --git a/Ruffles/Channeling/ChannelType.cs b/Ruffles/Channeling/ChannelType.cs index feb791a..56a0333 100644 --- a/Ruffles/Channeling/ChannelType.cs +++ b/Ruffles/Channeling/ChannelType.cs @@ -29,6 +29,7 @@ public enum ChannelType : byte /// Messages are not guaranteed to be delivered, the order is not guaranteed. /// Duplicate packets are not dropped. /// - UnreliableRaw + UnreliableRaw, + ReliableSequencedFragmented } } diff --git a/Ruffles/Channeling/Channels/ReliableChannel.cs b/Ruffles/Channeling/Channels/ReliableChannel.cs index 34d1bae..6b93f42 100644 --- a/Ruffles/Channeling/Channels/ReliableChannel.cs +++ b/Ruffles/Channeling/Channels/ReliableChannel.cs @@ -105,7 +105,9 @@ public HeapMemory HandlePoll() return null; } - public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dealloc) + private readonly HeapMemory[] SINGLE_MESSAGE_ARRAY = new HeapMemory[1]; + + public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { // Increment the sequence number _lastOutboundSequenceNumber++; @@ -138,7 +140,10 @@ public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dea // Tell the caller NOT to dealloc the memory, the channel needs it for resend purposes. dealloc = false; - return memory; + // Assign memory + SINGLE_MESSAGE_ARRAY[0] = memory; + + return SINGLE_MESSAGE_ARRAY; } public void HandleAck(ArraySegment payload) diff --git a/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs b/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs index 7b2b418..e0590d6 100644 --- a/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs +++ b/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs @@ -147,7 +147,9 @@ public HeapMemory HandlePoll() return null; } - public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dealloc) + private readonly HeapMemory[] SINGLE_MESSAGE_ARRAY = new HeapMemory[1]; + + public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { // Increment the sequence number _lastOutboundSequenceNumber++; @@ -180,7 +182,10 @@ public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dea // Tell the caller NOT to dealloc the memory, the channel needs it for resend purposes. dealloc = false; - return memory; + // Assign memory + SINGLE_MESSAGE_ARRAY[0] = memory; + + return SINGLE_MESSAGE_ARRAY; } public void HandleAck(ArraySegment payload) diff --git a/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs b/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs index 6009e3b..545d45c 100644 --- a/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs +++ b/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs @@ -252,6 +252,7 @@ public HeapMemory HandlePoll() SendAckEncoded(sequence, encodedFragment); hasMore = false; + return null; } else @@ -293,7 +294,7 @@ public HeapMemory HandlePoll() Fragments = newFragments, Alive = _receiveSequencer[sequence].Alive, Sequence = _receiveSequencer[sequence].Sequence, - Size = _receiveSequencer[sequence].Size + Size = isFinal ? (ushort?)(fragment + 1) : _receiveSequencer[sequence].Size }; } @@ -306,12 +307,12 @@ public HeapMemory HandlePoll() Fragments = new ArraySegment(_receiveSequencer[sequence].Fragments.Array, _receiveSequencer[sequence].Fragments.Offset, fragment + 1), Alive = _receiveSequencer[sequence].Alive, Sequence = _receiveSequencer[sequence].Sequence, - Size = _receiveSequencer[sequence].Size + Size = isFinal ? (ushort?)(fragment + 1) : _receiveSequencer[sequence].Size }; } } - + if (_receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment] == null || _receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment].isDead) { // Alloc some memory for the fragment HeapMemory memory = MemoryManager.Alloc((uint)payload.Count - 4); @@ -352,7 +353,7 @@ public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool d int messageSize = Math.Min(config.MaxMessageSize, payload.Count - position); // Allocate memory for each fragment - fragments[i] = MemoryManager.Alloc(((uint)Math.Min(config.MaxMessageSize, payload.Count - position)) + 6); + fragments[i] = MemoryManager.Alloc((uint)(messageSize + 6)); // Write headers fragments[i].Buffer[0] = HeaderPacker.Pack((byte)MessageType.Data, false); @@ -367,7 +368,7 @@ public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool d fragments[i].Buffer[5] = (byte)(((i & 32767) >> 8) | (byte)(i == fragments.Length - 1 ? 128 : 0)); // Write the payload - Buffer.BlockCopy(payload.Array, payload.Offset + position, fragments[i].Buffer, (int)fragments[i].VirtualOffset, messageSize); + Buffer.BlockCopy(payload.Array, payload.Offset + position, fragments[i].Buffer, 6, messageSize); // Increase the position position += messageSize; @@ -381,7 +382,7 @@ public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool d for (int i = 0; i < outgoingFragments.Length; i++) { - // Add the memory to the outgoing sequencer + // Add the memory to the outgoing sequencer array outgoingFragments[i] = new PendingOutgoingFragment() { Alive = true, @@ -393,6 +394,13 @@ public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool d }; } + // Add the memory to the outgoing sequencer + _sendSequencer[_lastOutboundSequenceNumber] = new PendingOutgoingPacket() + { + Alive = true, + Fragments = outgoingFragments + }; + return fragments; } @@ -525,24 +533,30 @@ public void InternalUpdate() { for (int j = 0; j < _sendSequencer[i].Fragments.Length; j++) { - if (_sendSequencer[i].Fragments[j].Attempts > config.ReliabilityMaxResendAttempts) + if (_sendSequencer[i].Fragments[j].Alive) { - // If they don't ack the message, disconnect them - connection.Disconnect(false); - } - else if ((DateTime.Now - _sendSequencer[i].Fragments[j].LastSent).TotalMilliseconds > connection.Roundtrip * config.ReliabilityResendRoundtripMultiplier) - { - _sendSequencer[i].Fragments[j] = new PendingOutgoingFragment() + if (_sendSequencer[i].Fragments[j].Attempts > config.ReliabilityMaxResendAttempts) + { + // If they don't ack the message, disconnect them + connection.Disconnect(false); + + return; + } + else if ((DateTime.Now - _sendSequencer[i].Fragments[j].LastSent).TotalMilliseconds > connection.Roundtrip * config.ReliabilityResendRoundtripMultiplier) { - Alive = true, - Attempts = (ushort)(_sendSequencer[i].Fragments[j].Attempts + 1), - LastSent = DateTime.Now, - FirstSent = _sendSequencer[i].Fragments[j].FirstSent, - Memory = _sendSequencer[i].Fragments[j].Memory, - Sequence = i - }; - - connection.SendRaw(new ArraySegment(_sendSequencer[i].Fragments[j].Memory.Buffer, (int)_sendSequencer[i].Fragments[j].Memory.VirtualOffset, (int)_sendSequencer[i].Fragments[j].Memory.VirtualCount), false); + _sendSequencer[i].Fragments[j] = new PendingOutgoingFragment() + { + Alive = true, + Attempts = (ushort)(_sendSequencer[i].Fragments[j].Attempts + 1), + LastSent = DateTime.Now, + FirstSent = _sendSequencer[i].Fragments[j].FirstSent, + Memory = _sendSequencer[i].Fragments[j].Memory, + Sequence = i + }; + + Console.WriteLine("Resending fragment for sequence: " + i + " and fragment: " + j); + connection.SendRaw(new ArraySegment(_sendSequencer[i].Fragments[j].Memory.Buffer, (int)_sendSequencer[i].Fragments[j].Memory.VirtualOffset, (int)_sendSequencer[i].Fragments[j].Memory.VirtualCount), false); + } } } } diff --git a/Ruffles/Channeling/Channels/UnreliableChannel.cs b/Ruffles/Channeling/Channels/UnreliableChannel.cs index 19eba67..6d2604c 100644 --- a/Ruffles/Channeling/Channels/UnreliableChannel.cs +++ b/Ruffles/Channeling/Channels/UnreliableChannel.cs @@ -28,7 +28,9 @@ internal UnreliableChannel(byte channelId, Connection connection, SocketConfig c _incomingAckedPackets = new SlidingWindow(config.ReliabilityWindowSize, true, sizeof(ushort)); } - public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dealloc) + private readonly HeapMemory[] SINGLE_MESSAGE_ARRAY = new HeapMemory[1]; + + public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { // Increment the sequence number _lastOutboundSequenceNumber++; @@ -50,7 +52,10 @@ public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dea // Tell the caller to deallc the memory dealloc = true; - return memory; + // Assign memory + SINGLE_MESSAGE_ARRAY[0] = memory; + + return SINGLE_MESSAGE_ARRAY; } public void HandleAck(ArraySegment payload) diff --git a/Ruffles/Channeling/Channels/UnreliableRawChannel.cs b/Ruffles/Channeling/Channels/UnreliableRawChannel.cs index 39494b6..df3b886 100644 --- a/Ruffles/Channeling/Channels/UnreliableRawChannel.cs +++ b/Ruffles/Channeling/Channels/UnreliableRawChannel.cs @@ -20,7 +20,9 @@ internal UnreliableRawChannel(byte channelId, Connection connection, SocketConfi this.config = config; } - public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dealloc) + private readonly HeapMemory[] SINGLE_MESSAGE_ARRAY = new HeapMemory[1]; + + public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { // Allocate the memory HeapMemory memory = MemoryManager.Alloc((uint)payload.Count + 2); @@ -35,7 +37,10 @@ public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dea // Tell the caller to deallc the memory dealloc = true; - return memory; + // Assign memory + SINGLE_MESSAGE_ARRAY[0] = memory; + + return SINGLE_MESSAGE_ARRAY; } public void HandleAck(ArraySegment payload) diff --git a/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs b/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs index 8296d1e..8c36179 100644 --- a/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs +++ b/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs @@ -23,7 +23,9 @@ internal UnreliableSequencedChannel(byte channelId, Connection connection) this.connection = connection; } - public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dealloc) + private readonly HeapMemory[] SINGLE_MESSAGE_ARRAY = new HeapMemory[1]; + + public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { // Increment the sequence number _lastOutboundSequenceNumber++; @@ -45,7 +47,10 @@ public HeapMemory CreateOutgoingMessage(ArraySegment payload, out bool dea // Tell the caller to deallc the memory dealloc = true; - return memory; + // Assign memory + SINGLE_MESSAGE_ARRAY[0] = memory; + + return SINGLE_MESSAGE_ARRAY; } internal HeapMemory CreateOutgoingHeartbeatMessage() diff --git a/Ruffles/Core/RuffleSocket.cs b/Ruffles/Core/RuffleSocket.cs index 3fcdc48..14651f0 100644 --- a/Ruffles/Core/RuffleSocket.cs +++ b/Ruffles/Core/RuffleSocket.cs @@ -256,7 +256,7 @@ public Connection Connect(EndPoint endpoint) for (byte i = 0; i < sizeof(ulong); i++) outgoingInternalBuffer[1 + sizeof(ulong) + i] = ((byte)(counter >> (i * 8))); // Write IV - for (byte i = 0; i < sizeof(ulong); i++) outgoingInternalBuffer[1 + sizeof(ulong) * 2 + i] = ((byte)(iv >> (i * 8))); + for (byte i = 0; i < sizeof(ulong); i++) outgoingInternalBuffer[1 + (sizeof(ulong) * 2) + i] = ((byte)(iv >> (i * 8))); } int minSize = 1 + (config.TimeBasedConnectionChallenge ? sizeof(ulong) * 3 : 0); @@ -405,13 +405,13 @@ private void CheckConnectionResends() if (config.TimeBasedConnectionChallenge) { // Write the response unix time - for (byte x = 0; x < sizeof(ulong); x++) outgoingInternalBuffer[1 + x] = ((byte)(Connections[i].PreConnectionChallengeTimestamp >> (i * 8))); + for (byte x = 0; x < sizeof(ulong); x++) outgoingInternalBuffer[1 + x] = ((byte)(Connections[i].PreConnectionChallengeTimestamp >> (x * 8))); // Write counter - for (byte x = 0; x < sizeof(ulong); x++) outgoingInternalBuffer[1 + sizeof(ulong) + x] = ((byte)(Connections[i].PreConnectionChallengeCounter >> (i * 8))); + for (byte x = 0; x < sizeof(ulong); x++) outgoingInternalBuffer[1 + sizeof(ulong) + x] = ((byte)(Connections[i].PreConnectionChallengeCounter >> (x * 8))); // Write IV - for (byte x = 0; x < sizeof(ulong); x++) outgoingInternalBuffer[1 + sizeof(ulong) * 2 + x] = ((byte)(Connections[i].PreConnectionChallengeIV >> (i * 8))); + for (byte x = 0; x < sizeof(ulong); x++) outgoingInternalBuffer[1 + (sizeof(ulong) * 2) + x] = ((byte)(Connections[i].PreConnectionChallengeIV >> (x * 8))); } int minSize = 1 + (config.TimeBasedConnectionChallenge ? sizeof(ulong) * 3 : 0); @@ -688,14 +688,14 @@ internal void HandlePacket(ArraySegment payload, EndPoint endpoint) ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) + 7] << 56)); // Read the initialization vector they used - ulong userIv = (((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2]) | - ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2 + 1] << 8) | - ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2 + 2] << 16) | - ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2 + 3] << 24) | - ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2 + 4] << 32) | - ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2 + 5] << 40) | - ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2 + 6] << 48) | - ((ulong)payload.Array[payload.Offset + 1 + sizeof(ulong) * 2 + 7] << 56)); + ulong userIv = (((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2)]) | + ((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2) + 1] << 8) | + ((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2) + 2] << 16) | + ((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2) + 3] << 24) | + ((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2) + 4] << 32) | + ((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2) + 5] << 40) | + ((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2) + 6] << 48) | + ((ulong)payload.Array[payload.Offset + 1 + (sizeof(ulong) * 2) + 7] << 56)); // Ensure they dont reuse a IV if (challengeInitializationVectors[userIv]) @@ -971,6 +971,11 @@ internal void HandlePacket(ArraySegment payload, EndPoint endpoint) pendingConnection.ChannelTypes[i] = ChannelType.UnreliableRaw; } break; + case (byte)ChannelType.ReliableSequencedFragmented: + { + pendingConnection.ChannelTypes[i] = ChannelType.ReliableSequencedFragmented; + } + break; default: { // Unknown channel type. Disconnect. @@ -1026,6 +1031,11 @@ internal void HandlePacket(ArraySegment payload, EndPoint endpoint) pendingConnection.Channels[i] = new UnreliableRawChannel(i, pendingConnection, config); } break; + case ChannelType.ReliableSequencedFragmented: + { + pendingConnection.Channels[i] = new ReliableSequencedFragmentedChannel(i, pendingConnection, this, config); + } + break; default: { // Unknown channel type. Disconnect. @@ -1348,6 +1358,11 @@ internal Connection AddNewConnection(EndPoint endpoint, ConnectionState state) connection.Channels[x] = new UnreliableRawChannel(x, connection, config); } break; + case ChannelType.ReliableSequencedFragmented: + { + connection.Channels[x] = new ReliableSequencedFragmentedChannel(x, connection, this, config); + } + break; default: { // Unknown channel type. Disconnect. diff --git a/Ruffles/Memory/MemoryManager.cs b/Ruffles/Memory/MemoryManager.cs index baf3b21..abe0d1e 100644 --- a/Ruffles/Memory/MemoryManager.cs +++ b/Ruffles/Memory/MemoryManager.cs @@ -42,18 +42,26 @@ internal static HeapMemory Alloc(uint size) _hasWarnedAboutLeak = true; } - return new HeapMemory(allocSize); - } + HeapMemory memory = new HeapMemory(allocSize); - HeapMemory memory = _pooledMemory.Dequeue(); + memory.isDead = false; + memory.VirtualCount = size; + memory.VirtualOffset = 0; + + return memory; + } + else + { + HeapMemory memory = _pooledMemory.Dequeue(); - memory.EnsureSize(allocSize); + memory.EnsureSize(allocSize); - memory.isDead = false; - memory.VirtualCount = allocSize; - memory.VirtualOffset = 0; + memory.isDead = false; + memory.VirtualCount = size; + memory.VirtualOffset = 0; - return memory; + return memory; + } } internal static void DeAlloc(HeapMemory memory) diff --git a/Ruffles/Messaging/PacketHandler.cs b/Ruffles/Messaging/PacketHandler.cs index b80e826..05c5c14 100644 --- a/Ruffles/Messaging/PacketHandler.cs +++ b/Ruffles/Messaging/PacketHandler.cs @@ -86,17 +86,23 @@ internal static void SendMessage(ArraySegment payload, Connection connecti IChannel channel = connection.Channels[channelId]; - HeapMemory messageMemory = channel.CreateOutgoingMessage(payload, out bool dealloc); + HeapMemory[] messageMemory = channel.CreateOutgoingMessage(payload, out bool dealloc); if (messageMemory != null) { - connection.SendRaw(new ArraySegment(messageMemory.Buffer, (int)messageMemory.VirtualOffset, (int)messageMemory.VirtualCount), noDelay); + for (int i = 0; i < messageMemory.Length; i++) + { + connection.SendRaw(new ArraySegment(messageMemory[i].Buffer, (int)messageMemory[i].VirtualOffset, (int)messageMemory[i].VirtualCount), noDelay); + } } if (dealloc) { // DeAlloc the memory again. This is done for unreliable channels that need the message after the initial send. - MemoryManager.DeAlloc(messageMemory); + for (int i = 0; i < messageMemory.Length; i++) + { + MemoryManager.DeAlloc(messageMemory[i]); + } } } } From e4b6a3d4ef74af2670ecc82c14b02a4d9a247fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Mon, 2 Sep 2019 20:43:57 +0200 Subject: [PATCH 05/19] Pooled pointer arrays --- .../Channeling/Channels/ReliableChannel.cs | 18 +-- .../Channels/ReliableSequencedChannel.cs | 26 +++-- .../ReliableSequencedFragmentedChannel .cs | 79 +++++++------ .../Channeling/Channels/UnreliableChannel.cs | 6 +- .../Channels/UnreliableRawChannel.cs | 6 +- .../Channels/UnreliableSequencedChannel.cs | 8 +- Ruffles/Configuration/SocketConfig.cs | 6 + Ruffles/Connections/Connection.cs | 5 +- Ruffles/Core/NetworkEvent.cs | 1 + Ruffles/Core/RuffleSocket.cs | 60 +++++----- Ruffles/Memory/IMemoryReleasable.cs | 2 +- Ruffles/Memory/MemoryManager.cs | 108 +++++++++++++++--- Ruffles/Messaging/HeapableSlidingWindow.cs | 8 +- Ruffles/Messaging/PacketHandler.cs | 14 ++- 14 files changed, 232 insertions(+), 115 deletions(-) diff --git a/Ruffles/Channeling/Channels/ReliableChannel.cs b/Ruffles/Channeling/Channels/ReliableChannel.cs index 6b93f42..7652da1 100644 --- a/Ruffles/Channeling/Channels/ReliableChannel.cs +++ b/Ruffles/Channeling/Channels/ReliableChannel.cs @@ -20,11 +20,11 @@ private struct PendingOutgoingPacket : IMemoryReleasable public DateTime FirstSent; public ushort Attempts; - public void DeAlloc() + public void DeAlloc(MemoryManager memoryManager) { if (IsAlloced) { - MemoryManager.DeAlloc(Memory); + memoryManager.DeAlloc(Memory); } } } @@ -41,14 +41,16 @@ public void DeAlloc() private readonly byte channelId; private readonly Connection connection; private readonly SocketConfig config; + private readonly MemoryManager memoryManager; - internal ReliableChannel(byte channelId, Connection connection, SocketConfig config) + internal ReliableChannel(byte channelId, Connection connection, SocketConfig config, MemoryManager memoryManager) { this.channelId = channelId; this.connection = connection; this.config = config; + this.memoryManager = memoryManager; - _sendSequencer = new HeapableSlidingWindow(config.ReliabilityWindowSize, true, sizeof(ushort)); + _sendSequencer = new HeapableSlidingWindow(config.ReliabilityWindowSize, true, sizeof(ushort), memoryManager); } public HeapMemory HandlePoll() @@ -113,7 +115,7 @@ public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool d _lastOutboundSequenceNumber++; // Allocate the memory - HeapMemory memory = MemoryManager.Alloc((uint)payload.Count + 4); + HeapMemory memory = memoryManager.AllocHeapMemory((uint)payload.Count + 4); // Write headers memory.Buffer[0] = HeaderPacker.Pack((byte)MessageType.Data, false); @@ -154,7 +156,7 @@ public void HandleAck(ArraySegment payload) if (_sendSequencer[sequence].Alive) { // Dealloc the memory held by the sequencer - MemoryManager.DeAlloc(_sendSequencer[sequence].Memory); + memoryManager.DeAlloc(_sendSequencer[sequence].Memory); // TODO: Remove roundtripping from channeled packets and make specific ping-pong packets @@ -223,7 +225,7 @@ public void Reset() private void SendAck(ushort sequence) { // Alloc ack memory - HeapMemory ackMemory = MemoryManager.Alloc(4); + HeapMemory ackMemory = memoryManager.AllocHeapMemory(4); // Write header ackMemory.Buffer[0] = HeaderPacker.Pack((byte)MessageType.Ack, false); @@ -237,7 +239,7 @@ private void SendAck(ushort sequence) connection.SendRaw(new ArraySegment(ackMemory.Buffer, 0, 4), false); // Return memory - MemoryManager.DeAlloc(ackMemory); + memoryManager.DeAlloc(ackMemory); } } } diff --git a/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs b/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs index e0590d6..d5a9cbf 100644 --- a/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs +++ b/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs @@ -20,11 +20,11 @@ internal struct PendingOutgoingPacket : IMemoryReleasable public ushort Attempts; public bool Alive; - public void DeAlloc() + public void DeAlloc(MemoryManager memoryManager) { if (IsAlloced) { - MemoryManager.DeAlloc(Memory); + memoryManager.DeAlloc(Memory); } } } @@ -37,11 +37,11 @@ internal struct PendingIncomingPacket : IMemoryReleasable public HeapMemory Memory; public bool Alive; - public void DeAlloc() + public void DeAlloc(MemoryManager memoryManager) { if (IsAlloced) { - MemoryManager.DeAlloc(Memory); + memoryManager.DeAlloc(Memory); } } } @@ -59,17 +59,19 @@ public void DeAlloc() private readonly Connection connection; private readonly RuffleSocket socket; private readonly SocketConfig config; + private readonly MemoryManager memoryManager; - internal ReliableSequencedChannel(byte channelId, Connection connection, RuffleSocket socket, SocketConfig config) + internal ReliableSequencedChannel(byte channelId, Connection connection, RuffleSocket socket, SocketConfig config, MemoryManager memoryManager) { this.channelId = channelId; this.connection = connection; this.socket = socket; this.config = config; + this.memoryManager = memoryManager; // Alloc the in flight windows for receive and send - _receiveSequencer = new HeapableSlidingWindow(config.ReliabilityWindowSize, true, sizeof(ushort)); - _sendSequencer = new HeapableSlidingWindow(config.ReliabilityWindowSize, true, sizeof(ushort)); + _receiveSequencer = new HeapableSlidingWindow(config.ReliabilityWindowSize, true, sizeof(ushort), memoryManager); + _sendSequencer = new HeapableSlidingWindow(config.ReliabilityWindowSize, true, sizeof(ushort), memoryManager); } public HeapMemory HandlePoll() @@ -126,7 +128,7 @@ public HeapMemory HandlePoll() else if (SequencingUtils.Distance(sequence, _incomingLowestAckedSequence, sizeof(ushort)) > 0 && !_receiveSequencer[sequence].Alive) { // Alloc payload plus header memory - HeapMemory memory = MemoryManager.Alloc((uint)payload.Count - 2); + HeapMemory memory = memoryManager.AllocHeapMemory((uint)payload.Count - 2); // Copy the payload Buffer.BlockCopy(payload.Array, payload.Offset + 2, memory.Buffer, 0, payload.Count - 2); @@ -155,7 +157,7 @@ public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool d _lastOutboundSequenceNumber++; // Allocate the memory - HeapMemory memory = MemoryManager.Alloc((uint)payload.Count + 4); + HeapMemory memory = memoryManager.AllocHeapMemory((uint)payload.Count + 4); // Write headers memory.Buffer[0] = HeaderPacker.Pack((byte)MessageType.Data, false); @@ -196,7 +198,7 @@ public void HandleAck(ArraySegment payload) if (_sendSequencer[sequence].Alive) { // Dealloc the memory held by the sequencer for the packet - _sendSequencer[sequence].DeAlloc(); + _sendSequencer[sequence].DeAlloc(memoryManager); // TODO: Remove roundtripping from channeled packets and make specific ping-pong packets @@ -234,7 +236,7 @@ public void Reset() private void SendAck(ushort sequence) { // Alloc ack memory - HeapMemory ackMemory = MemoryManager.Alloc(4); + HeapMemory ackMemory = memoryManager.AllocHeapMemory(4); // Write header ackMemory.Buffer[0] = HeaderPacker.Pack((byte)MessageType.Ack, false); @@ -248,7 +250,7 @@ private void SendAck(ushort sequence) connection.SendRaw(new ArraySegment(ackMemory.Buffer, 0, 4), false); // Return memory - MemoryManager.DeAlloc(ackMemory); + memoryManager.DeAlloc(ackMemory); } public void InternalUpdate() diff --git a/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs b/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs index 545d45c..4312ed0 100644 --- a/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs +++ b/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs @@ -37,7 +37,7 @@ public bool IsAlloced public PendingOutgoingFragment[] Fragments; public bool Alive; - public void DeAlloc() + public void DeAlloc(MemoryManager memoryManager) { if (IsAlloced) { @@ -45,7 +45,7 @@ public void DeAlloc() { if (Fragments[i].Alive && Fragments[i].IsAlloced) { - Fragments[i].DeAlloc(); + Fragments[i].DeAlloc(memoryManager); } } } @@ -64,11 +64,11 @@ internal struct PendingOutgoingFragment : IMemoryReleasable public ushort Attempts; public bool Alive; - public void DeAlloc() + public void DeAlloc(MemoryManager memoryManager) { if (IsAlloced) { - MemoryManager.DeAlloc(Memory); + memoryManager.DeAlloc(Memory); } } } @@ -86,7 +86,7 @@ public bool IsAlloced for (int i = 0; i < Fragments.Count; i++) { - if (Fragments.Array[Fragments.Offset + i] != null && !Fragments.Array[Fragments.Offset + i].isDead) + if (Fragments.Array[Fragments.Offset + i] != null && !((HeapMemory)Fragments.Array[Fragments.Offset + i]).isDead) { return true; } @@ -107,7 +107,7 @@ public bool IsComplete for (int i = 0; i < Fragments.Count; i++) { - if (Fragments.Array[Fragments.Offset + i] == null || Fragments.Array[Fragments.Offset + i].isDead) + if (Fragments.Array[Fragments.Offset + i] == null || ((HeapMemory)Fragments.Array[Fragments.Offset + i]).isDead) { return false; } @@ -132,7 +132,7 @@ public uint TotalByteSize for (int i = 0; i < Fragments.Count; i++) { - byteSize += Fragments.Array[Fragments.Offset + i].VirtualCount; + byteSize += ((HeapMemory)Fragments.Array[Fragments.Offset + i]).VirtualCount; } return byteSize; @@ -141,20 +141,22 @@ public uint TotalByteSize public ushort Sequence; public ushort? Size; - public ArraySegment Fragments; + public ArraySegment Fragments; public bool Alive; - public void DeAlloc() + public void DeAlloc(MemoryManager memoryManager) { if (IsAlloced) { for (int i = 0; i < Fragments.Count; i++) { - if (Fragments.Array[Fragments.Offset + i] != null && !Fragments.Array[Fragments.Offset + i].isDead) + if (Fragments.Array[Fragments.Offset + i] != null && !((HeapMemory)Fragments.Array[Fragments.Offset + i]).isDead) { - MemoryManager.DeAlloc(Fragments.Array[Fragments.Offset + i]); + memoryManager.DeAlloc((HeapMemory)Fragments.Array[Fragments.Offset + i]); } } + + memoryManager.DeAlloc(Fragments.Array); } } } @@ -172,17 +174,19 @@ public void DeAlloc() private readonly Connection connection; private readonly RuffleSocket socket; private readonly SocketConfig config; + private readonly MemoryManager memoryManager; - internal ReliableSequencedFragmentedChannel(byte channelId, Connection connection, RuffleSocket socket, SocketConfig config) + internal ReliableSequencedFragmentedChannel(byte channelId, Connection connection, RuffleSocket socket, SocketConfig config, MemoryManager memoryManager) { this.channelId = channelId; this.connection = connection; this.socket = socket; this.config = config; + this.memoryManager = memoryManager; // Alloc the in flight windows for receive and send - _receiveSequencer = new HeapableSlidingWindow(config.ReliabilityWindowSize, true, sizeof(ushort)); - _sendSequencer = new HeapableSlidingWindow(config.ReliabilityWindowSize, true, sizeof(ushort)); + _receiveSequencer = new HeapableSlidingWindow(config.ReliabilityWindowSize, true, sizeof(ushort), memoryManager); + _sendSequencer = new HeapableSlidingWindow(config.ReliabilityWindowSize, true, sizeof(ushort), memoryManager); } public HeapMemory HandlePoll() @@ -198,7 +202,7 @@ public HeapMemory HandlePoll() uint totalSize = nextPacket.TotalByteSize; // Alloc memory for that large segment - HeapMemory memory = MemoryManager.Alloc(totalSize); + HeapMemory memory = memoryManager.AllocHeapMemory(totalSize); // Keep track of where we are, fragments COULD have different sizes. int bufferPosition = 0; @@ -207,13 +211,13 @@ public HeapMemory HandlePoll() for (int i = 0; i < nextPacket.Fragments.Count; i++) { // Copy fragment to final buffer - Buffer.BlockCopy(nextPacket.Fragments.Array[nextPacket.Fragments.Offset + i].Buffer, (int)nextPacket.Fragments.Array[nextPacket.Fragments.Offset + i].VirtualOffset, memory.Buffer, bufferPosition, (int)nextPacket.Fragments.Array[nextPacket.Fragments.Offset + i].VirtualCount); + Buffer.BlockCopy(((HeapMemory)nextPacket.Fragments.Array[nextPacket.Fragments.Offset + i]).Buffer, (int)((HeapMemory)nextPacket.Fragments.Array[nextPacket.Fragments.Offset + i]).VirtualOffset, memory.Buffer, bufferPosition, (int)((HeapMemory)nextPacket.Fragments.Array[nextPacket.Fragments.Offset + i]).VirtualCount); - bufferPosition += (int)nextPacket.Fragments.Array[nextPacket.Fragments.Offset + i].VirtualCount; + bufferPosition += (int)((HeapMemory)nextPacket.Fragments.Array[nextPacket.Fragments.Offset + i]).VirtualCount; } // Free the memory of all the individual fragments - nextPacket.DeAlloc(); + nextPacket.DeAlloc(memoryManager); // Kill _receiveSequencer[_incomingLowestAckedSequence] = new PendingIncomingPacket() @@ -221,7 +225,7 @@ public HeapMemory HandlePoll() Alive = false, Sequence = 0, Size = null, - Fragments = new ArraySegment() + Fragments = new ArraySegment() }; @@ -245,7 +249,7 @@ public HeapMemory HandlePoll() if (SequencingUtils.Distance(sequence, _incomingLowestAckedSequence, sizeof(ushort)) <= 0 || (_receiveSequencer[sequence].Alive && _receiveSequencer[sequence].IsComplete) || - (_receiveSequencer[sequence].Alive && _receiveSequencer[sequence].Fragments.Array != null && _receiveSequencer[sequence].Fragments.Count > fragment && _receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment] != null && !_receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment].isDead)) + (_receiveSequencer[sequence].Alive && _receiveSequencer[sequence].Fragments.Array != null && _receiveSequencer[sequence].Fragments.Count > fragment && _receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment] != null && !((HeapMemory)_receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment]).isDead)) { // We have already acked this message. Ack again @@ -262,11 +266,14 @@ public HeapMemory HandlePoll() // If this is the first fragment we ever get, index the data. if (!_receiveSequencer[sequence].Alive) { + int fragmentPointerSize = isFinal ? fragment + 1 : config.FragmentArrayBaseSize; + + object[] fragmentPointers = memoryManager.AllocPointers((uint)fragmentPointerSize); + _receiveSequencer[sequence] = new PendingIncomingPacket() { Alive = true, - // TODO: Remove hardcoded values - Fragments = new ArraySegment(new HeapMemory[isFinal ? fragment + 1 : 128], 0, fragment + 1), + Fragments = new ArraySegment(fragmentPointers, 0, fragment + 1), Sequence = sequence, Size = isFinal ? (ushort?)(fragment + 1) : null }; @@ -279,15 +286,17 @@ public HeapMemory HandlePoll() // We need to expand the fragments array. // Calculate the target size of the array - // TODO: Remove hardcoded values - uint allocSize = Math.Max(128, MemoryManager.CalculateMultiple((uint)fragment + 1, 64)); + uint allocSize = Math.Max(config.FragmentArrayBaseSize, MemoryManager.CalculateMultiple((uint)fragment + 1, 64)); // Alloc new array - ArraySegment newFragments = new ArraySegment(new HeapMemory[allocSize], _receiveSequencer[sequence].Fragments.Offset, fragment + 1); + ArraySegment newFragments = new ArraySegment(new HeapMemory[allocSize], _receiveSequencer[sequence].Fragments.Offset, fragment + 1); // Copy old values Array.Copy(_receiveSequencer[sequence].Fragments.Array, newFragments.Array, _receiveSequencer[sequence].Fragments.Array.Length); + // Return the memory for the old + memoryManager.DeAlloc(_receiveSequencer[sequence].Fragments.Array); + // Update the index _receiveSequencer[sequence] = new PendingIncomingPacket() { @@ -304,7 +313,7 @@ public HeapMemory HandlePoll() // Update the virtual elngth of the array _receiveSequencer[sequence] = new PendingIncomingPacket() { - Fragments = new ArraySegment(_receiveSequencer[sequence].Fragments.Array, _receiveSequencer[sequence].Fragments.Offset, fragment + 1), + Fragments = new ArraySegment(_receiveSequencer[sequence].Fragments.Array, _receiveSequencer[sequence].Fragments.Offset, fragment + 1), Alive = _receiveSequencer[sequence].Alive, Sequence = _receiveSequencer[sequence].Sequence, Size = isFinal ? (ushort?)(fragment + 1) : _receiveSequencer[sequence].Size @@ -312,10 +321,10 @@ public HeapMemory HandlePoll() } } - if (_receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment] == null || _receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment].isDead) + if (_receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment] == null || ((HeapMemory)_receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment]).isDead) { // Alloc some memory for the fragment - HeapMemory memory = MemoryManager.Alloc((uint)payload.Count - 4); + HeapMemory memory = memoryManager.AllocHeapMemory((uint)payload.Count - 4); // Copy the payload Buffer.BlockCopy(payload.Array, payload.Offset + 4, memory.Buffer, 0, payload.Count - 4); @@ -353,7 +362,7 @@ public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool d int messageSize = Math.Min(config.MaxMessageSize, payload.Count - position); // Allocate memory for each fragment - fragments[i] = MemoryManager.Alloc((uint)(messageSize + 6)); + fragments[i] = memoryManager.AllocHeapMemory((uint)(messageSize + 6)); // Write headers fragments[i].Buffer[0] = HeaderPacker.Pack((byte)MessageType.Data, false); @@ -418,7 +427,7 @@ public void HandleAck(ArraySegment payload) if (_sendSequencer[sequence].Alive && _sendSequencer[sequence].Fragments.Length > fragment && _sendSequencer[sequence].Fragments[fragment].Alive) { // Dealloc the memory held by the sequencer for the packet - _sendSequencer[sequence].Fragments[fragment].DeAlloc(); + _sendSequencer[sequence].Fragments[fragment].DeAlloc(memoryManager); // TODO: Remove roundtripping from channeled packets and make specific ping-pong packets @@ -448,7 +457,7 @@ public void HandleAck(ArraySegment payload) if (!hasAllocatedAndAliveFragments) { // Dealloc the wrapper packet - _sendSequencer[sequence].DeAlloc(); + _sendSequencer[sequence].DeAlloc(memoryManager); // Kill the wrapper packet _sendSequencer[sequence] = new PendingOutgoingPacket() @@ -478,7 +487,7 @@ public void Reset() private void SendAck(ushort sequence, ushort fragment, bool isFinal) { // Alloc ack memory - HeapMemory ackMemory = MemoryManager.Alloc(4); + HeapMemory ackMemory = memoryManager.AllocHeapMemory(4); // Write header ackMemory.Buffer[0] = HeaderPacker.Pack((byte)MessageType.Ack, false); @@ -496,13 +505,13 @@ private void SendAck(ushort sequence, ushort fragment, bool isFinal) connection.SendRaw(new ArraySegment(ackMemory.Buffer, 0, 6), false); // Return memory - MemoryManager.DeAlloc(ackMemory); + memoryManager.DeAlloc(ackMemory); } private void SendAckEncoded(ushort sequence, ushort encodedFragment) { // Alloc ack memory - HeapMemory ackMemory = MemoryManager.Alloc(4); + HeapMemory ackMemory = memoryManager.AllocHeapMemory(4); // Write header ackMemory.Buffer[0] = HeaderPacker.Pack((byte)MessageType.Ack, false); @@ -520,7 +529,7 @@ private void SendAckEncoded(ushort sequence, ushort encodedFragment) connection.SendRaw(new ArraySegment(ackMemory.Buffer, 0, 6), false); // Return memory - MemoryManager.DeAlloc(ackMemory); + memoryManager.DeAlloc(ackMemory); } public void InternalUpdate() diff --git a/Ruffles/Channeling/Channels/UnreliableChannel.cs b/Ruffles/Channeling/Channels/UnreliableChannel.cs index 6d2604c..a2330b0 100644 --- a/Ruffles/Channeling/Channels/UnreliableChannel.cs +++ b/Ruffles/Channeling/Channels/UnreliableChannel.cs @@ -18,12 +18,14 @@ internal class UnreliableChannel : IChannel private readonly byte channelId; private readonly Connection connection; private readonly SocketConfig config; + private readonly MemoryManager memoryManager; - internal UnreliableChannel(byte channelId, Connection connection, SocketConfig config) + internal UnreliableChannel(byte channelId, Connection connection, SocketConfig config, MemoryManager memoryManager) { this.channelId = channelId; this.connection = connection; this.config = config; + this.memoryManager = memoryManager; _incomingAckedPackets = new SlidingWindow(config.ReliabilityWindowSize, true, sizeof(ushort)); } @@ -36,7 +38,7 @@ public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool d _lastOutboundSequenceNumber++; // Allocate the memory - HeapMemory memory = MemoryManager.Alloc((uint)payload.Count + 4); + HeapMemory memory = memoryManager.AllocHeapMemory((uint)payload.Count + 4); // Write headers memory.Buffer[0] = HeaderPacker.Pack((byte)MessageType.Data, false); diff --git a/Ruffles/Channeling/Channels/UnreliableRawChannel.cs b/Ruffles/Channeling/Channels/UnreliableRawChannel.cs index df3b886..a9aa4e1 100644 --- a/Ruffles/Channeling/Channels/UnreliableRawChannel.cs +++ b/Ruffles/Channeling/Channels/UnreliableRawChannel.cs @@ -12,12 +12,14 @@ internal class UnreliableRawChannel : IChannel private readonly byte channelId; private readonly Connection connection; private readonly SocketConfig config; + private readonly MemoryManager memoryManager; - internal UnreliableRawChannel(byte channelId, Connection connection, SocketConfig config) + internal UnreliableRawChannel(byte channelId, Connection connection, SocketConfig config, MemoryManager memoryManager) { this.channelId = channelId; this.connection = connection; this.config = config; + this.memoryManager = memoryManager; } private readonly HeapMemory[] SINGLE_MESSAGE_ARRAY = new HeapMemory[1]; @@ -25,7 +27,7 @@ internal UnreliableRawChannel(byte channelId, Connection connection, SocketConfi public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { // Allocate the memory - HeapMemory memory = MemoryManager.Alloc((uint)payload.Count + 2); + HeapMemory memory = memoryManager.AllocHeapMemory((uint)payload.Count + 2); // Write headers memory.Buffer[0] = HeaderPacker.Pack((byte)MessageType.Data, false); diff --git a/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs b/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs index 8c36179..96184cc 100644 --- a/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs +++ b/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs @@ -16,11 +16,13 @@ internal class UnreliableSequencedChannel : IChannel // Channel info private readonly byte channelId; private readonly Connection connection; + private readonly MemoryManager memoryManager; - internal UnreliableSequencedChannel(byte channelId, Connection connection) + internal UnreliableSequencedChannel(byte channelId, Connection connection, MemoryManager memoryManager) { this.channelId = channelId; this.connection = connection; + this.memoryManager = memoryManager; } private readonly HeapMemory[] SINGLE_MESSAGE_ARRAY = new HeapMemory[1]; @@ -31,7 +33,7 @@ public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool d _lastOutboundSequenceNumber++; // Allocate the memory - HeapMemory memory = MemoryManager.Alloc((uint)payload.Count + 4); + HeapMemory memory = memoryManager.AllocHeapMemory((uint)payload.Count + 4); // Write headers memory.Buffer[0] = HeaderPacker.Pack((byte)MessageType.Data, false); @@ -59,7 +61,7 @@ internal HeapMemory CreateOutgoingHeartbeatMessage() _lastOutboundSequenceNumber++; // Allocate the memory - HeapMemory memory = MemoryManager.Alloc(3); + HeapMemory memory = memoryManager.AllocHeapMemory(3); // Write headers memory.Buffer[0] = HeaderPacker.Pack((byte)MessageType.Heartbeat, false); diff --git a/Ruffles/Configuration/SocketConfig.cs b/Ruffles/Configuration/SocketConfig.cs index bd90497..553741e 100644 --- a/Ruffles/Configuration/SocketConfig.cs +++ b/Ruffles/Configuration/SocketConfig.cs @@ -40,6 +40,8 @@ public class SocketConfig /// public bool ReuseConnections = true; + public bool PoolPointerArrays = false; + // Bandwidth /// /// The maximum size of a merged packet. @@ -50,7 +52,11 @@ public class SocketConfig /// The maximum delay before merged packets are sent. /// public ulong MaxMergeDelay = 250; + + // Fragmentation public ushort MaxMessageSize = 1400; + public ushort FragmentArrayBaseSize = 64; + public ushort MaxFragments = 32768; // Memory /// diff --git a/Ruffles/Connections/Connection.cs b/Ruffles/Connections/Connection.cs index c4173b8..a53b321 100644 --- a/Ruffles/Connections/Connection.cs +++ b/Ruffles/Connections/Connection.cs @@ -4,6 +4,7 @@ using Ruffles.Channeling.Channels; using Ruffles.Configuration; using Ruffles.Core; +using Ruffles.Memory; using Ruffles.Messaging; namespace Ruffles.Connections @@ -79,11 +80,11 @@ public class Connection internal DateTime HandshakeLastSendTime; - internal Connection(SocketConfig config) + internal Connection(SocketConfig config, MemoryManager memoryManager) { if (config.EnableHeartbeats) { - HeartbeatChannel = new UnreliableSequencedChannel(0, this); + HeartbeatChannel = new UnreliableSequencedChannel(0, this, memoryManager); } } diff --git a/Ruffles/Core/NetworkEvent.cs b/Ruffles/Core/NetworkEvent.cs index 113a3dc..59968ae 100644 --- a/Ruffles/Core/NetworkEvent.cs +++ b/Ruffles/Core/NetworkEvent.cs @@ -44,6 +44,7 @@ public struct NetworkEvent public byte ChannelId { get; internal set; } internal HeapMemory InternalMemory; + internal MemoryManager MemoryManager; internal bool AllowUserRecycle; /// diff --git a/Ruffles/Core/RuffleSocket.cs b/Ruffles/Core/RuffleSocket.cs index 14651f0..f12bf01 100644 --- a/Ruffles/Core/RuffleSocket.cs +++ b/Ruffles/Core/RuffleSocket.cs @@ -30,7 +30,7 @@ public class RuffleSocket internal readonly Queue UserEventQueue = new Queue(); - private ushort PendingConnections = 0; + private ushort pendingConnections = 0; private Socket ipv4Socket; private Socket ipv6Socket; @@ -43,6 +43,8 @@ public class RuffleSocket private readonly SlidingSet challengeInitializationVectors; + private readonly MemoryManager memoryManager; + public RuffleSocket(SocketConfig config) { this.config = config; @@ -57,6 +59,8 @@ public RuffleSocket(SocketConfig config) Connections = new Connection[config.MaxConnections]; challengeInitializationVectors = new SlidingSet((int)config.ConnectionChallengeHistory, true); + memoryManager = new MemoryManager(config); + bool bindSuccess = Bind(config.IPv4ListenAddress, config.IPv6ListenAddress, config.DualListenPort, config.UseIPv6Dual); if (!bindSuccess) @@ -178,7 +182,7 @@ public void Send(ArraySegment payload, Connection connection, byte channel throw new InvalidOperationException("Connection is not connected"); } - PacketHandler.SendMessage(payload, connection, channelId, noDelay); + PacketHandler.SendMessage(payload, connection, channelId, noDelay, memoryManager); } /// @@ -519,7 +523,7 @@ private void CheckConnectionHeartbeats() Connections[i].SendRaw(new ArraySegment(heartbeatMemory.Buffer, (int)heartbeatMemory.VirtualOffset, (int)heartbeatMemory.VirtualCount), false); // DeAlloc the memory - MemoryManager.DeAlloc(heartbeatMemory); + memoryManager.DeAlloc(heartbeatMemory); } } } @@ -568,7 +572,8 @@ public NetworkEvent Poll() InternalMemory = null, Type = NetworkEventType.Nothing, ChannelId = 0, - SocketReceiveTime = DateTime.Now + SocketReceiveTime = DateTime.Now, + MemoryManager = memoryManager }; } @@ -880,7 +885,8 @@ internal void HandlePacket(ArraySegment payload, EndPoint endpoint) ChannelId = 0, Data = new ArraySegment(), InternalMemory = null, - SocketReceiveTime = DateTime.Now + SocketReceiveTime = DateTime.Now, + MemoryManager = memoryManager }); } else @@ -1008,32 +1014,32 @@ internal void HandlePacket(ArraySegment payload, EndPoint endpoint) { case ChannelType.Reliable: { - pendingConnection.Channels[i] = new ReliableChannel(i, pendingConnection, config); + pendingConnection.Channels[i] = new ReliableChannel(i, pendingConnection, config, memoryManager); } break; case ChannelType.Unreliable: { - pendingConnection.Channels[i] = new UnreliableChannel(i, pendingConnection, config); + pendingConnection.Channels[i] = new UnreliableChannel(i, pendingConnection, config, memoryManager); } break; case ChannelType.UnreliableSequenced: { - pendingConnection.Channels[i] = new UnreliableSequencedChannel(i, pendingConnection); + pendingConnection.Channels[i] = new UnreliableSequencedChannel(i, pendingConnection, memoryManager); } break; case ChannelType.ReliableSequenced: { - pendingConnection.Channels[i] = new ReliableSequencedChannel(i, pendingConnection, this, config); + pendingConnection.Channels[i] = new ReliableSequencedChannel(i, pendingConnection, this, config, memoryManager); } break; case ChannelType.UnreliableRaw: { - pendingConnection.Channels[i] = new UnreliableRawChannel(i, pendingConnection, config); + pendingConnection.Channels[i] = new UnreliableRawChannel(i, pendingConnection, config, memoryManager); } break; case ChannelType.ReliableSequencedFragmented: { - pendingConnection.Channels[i] = new ReliableSequencedFragmentedChannel(i, pendingConnection, this, config); + pendingConnection.Channels[i] = new ReliableSequencedFragmentedChannel(i, pendingConnection, this, config, memoryManager); } break; default: @@ -1061,7 +1067,8 @@ internal void HandlePacket(ArraySegment payload, EndPoint endpoint) ChannelId = 0, Data = new ArraySegment(), InternalMemory = null, - SocketReceiveTime = DateTime.Now + SocketReceiveTime = DateTime.Now, + MemoryManager = memoryManager }); // Send the confirmation @@ -1126,7 +1133,7 @@ internal void HandlePacket(ArraySegment payload, EndPoint endpoint) { connection.LastMessageIn = DateTime.Now; - PacketHandler.HandleIncomingMessage(new ArraySegment(payload.Array, payload.Offset + 1, payload.Count - 1), connection, config); + PacketHandler.HandleIncomingMessage(new ArraySegment(payload.Array, payload.Offset + 1, payload.Count - 1), connection, config, memoryManager); } else { @@ -1186,7 +1193,7 @@ internal void ConnectPendingConnection(Connection connection) connection.State = ConnectionState.Connected; - PendingConnections++; + pendingConnections++; } internal Connection GetPendingConnection(EndPoint endpoint) @@ -1260,7 +1267,7 @@ internal void DisconnectConnection(Connection connection, bool sendMessage, bool { AddressPendingConnectionLookup.Remove(connection.EndPoint); - PendingConnections--; + pendingConnections--; } else { @@ -1277,14 +1284,15 @@ internal void DisconnectConnection(Connection connection, bool sendMessage, bool ChannelId = 0, Data = new ArraySegment(), InternalMemory = null, - SocketReceiveTime = DateTime.Now + SocketReceiveTime = DateTime.Now, + MemoryManager = memoryManager }); } internal Connection AddNewConnection(EndPoint endpoint, ConnectionState state) { // Make sure they are not already connected to prevent an attack where a single person can fill all the slots. - if (AddressPendingConnectionLookup.ContainsKey(endpoint) || AddressConnectionLookup.ContainsKey(endpoint) || PendingConnections > config.MaxPendingConnections) + if (AddressPendingConnectionLookup.ContainsKey(endpoint) || AddressConnectionLookup.ContainsKey(endpoint) || pendingConnections > config.MaxPendingConnections) { return null; } @@ -1296,7 +1304,7 @@ internal Connection AddNewConnection(EndPoint endpoint, ConnectionState state) if (Connections[i] == null) { // Alloc on the heap - connection = new Connection(config) + connection = new Connection(config, memoryManager) { Dead = false, Recycled = false, @@ -1335,32 +1343,32 @@ internal Connection AddNewConnection(EndPoint endpoint, ConnectionState state) { case ChannelType.Reliable: { - connection.Channels[x] = new ReliableChannel(x, connection, config); + connection.Channels[x] = new ReliableChannel(x, connection, config, memoryManager); } break; case ChannelType.Unreliable: { - connection.Channels[x] = new UnreliableChannel(x, connection, config); + connection.Channels[x] = new UnreliableChannel(x, connection, config, memoryManager); } break; case ChannelType.UnreliableSequenced: { - connection.Channels[x] = new UnreliableSequencedChannel(x, connection); + connection.Channels[x] = new UnreliableSequencedChannel(x, connection, memoryManager); } break; case ChannelType.ReliableSequenced: { - connection.Channels[x] = new ReliableSequencedChannel(x, connection, this, config); + connection.Channels[x] = new ReliableSequencedChannel(x, connection, this, config, memoryManager); } break; case ChannelType.UnreliableRaw: { - connection.Channels[x] = new UnreliableRawChannel(x, connection, config); + connection.Channels[x] = new UnreliableRawChannel(x, connection, config, memoryManager); } break; case ChannelType.ReliableSequencedFragmented: { - connection.Channels[x] = new ReliableSequencedFragmentedChannel(x, connection, this, config); + connection.Channels[x] = new ReliableSequencedFragmentedChannel(x, connection, this, config, memoryManager); } break; default: @@ -1377,7 +1385,7 @@ internal Connection AddNewConnection(EndPoint endpoint, ConnectionState state) Connections[i] = connection; AddressPendingConnectionLookup.Add(endpoint, connection); - PendingConnections++; + pendingConnections++; break; } @@ -1403,7 +1411,7 @@ internal Connection AddNewConnection(EndPoint endpoint, ConnectionState state) AddressPendingConnectionLookup.Add(endpoint, connection); - PendingConnections++; + pendingConnections++; break; } diff --git a/Ruffles/Memory/IMemoryReleasable.cs b/Ruffles/Memory/IMemoryReleasable.cs index 61cfa20..8ca040c 100644 --- a/Ruffles/Memory/IMemoryReleasable.cs +++ b/Ruffles/Memory/IMemoryReleasable.cs @@ -3,6 +3,6 @@ internal interface IMemoryReleasable { bool IsAlloced { get; } - void DeAlloc(); + void DeAlloc(MemoryManager memoryManager); } } diff --git a/Ruffles/Memory/MemoryManager.cs b/Ruffles/Memory/MemoryManager.cs index abe0d1e..e82ad95 100644 --- a/Ruffles/Memory/MemoryManager.cs +++ b/Ruffles/Memory/MemoryManager.cs @@ -1,17 +1,28 @@ using System; using System.Collections.Generic; +using Ruffles.Configuration; using Ruffles.Exceptions; +using Ruffles.Utils; namespace Ruffles.Memory { - internal static class MemoryManager + internal class MemoryManager : IDisposable { - private static ushort _createdPools = 0; - private static bool _hasWarnedAboutLeak = false; - private static readonly Queue _pooledMemory = new Queue(); + private ushort _createdHeapMemory = 0; + private bool _hasWarnedAboutHeapMemoryLeaks = false; + private readonly Queue _pooledHeapMemory = new Queue(); - private const uint minBufferSize = 64; - private const uint bufferMultiple = 64; + private const uint minHeapMemorySize = 64; + private const uint heapMemorySizeMultiple = 64; + + private ushort _createdPointerArrays = 0; + private bool _hasWarnedAboutPointerArrayLeaks = false; + private readonly List _pooledPointerArrays = new List(); + + private const uint minPointerArraySize = 64; + private const uint pointerArraySizeMultiple = 64; + + private readonly SocketConfig _configuration; internal static uint CalculateMultiple(uint minSize, uint multiple) { @@ -27,19 +38,70 @@ internal static uint CalculateMultiple(uint minSize, uint multiple) return result; } + + + internal MemoryManager(SocketConfig config) + { + _configuration = config; + } + + internal object[] AllocPointers(uint size) + { + uint allocSize = Math.Max(minPointerArraySize, CalculateMultiple(size, pointerArraySizeMultiple)); + + object[] pointers = null; + + if (_configuration.PoolPointerArrays) + { + for (int i = 0; i < _pooledPointerArrays.Count; i++) + { + if (_pooledPointerArrays[i].Length >= size) + { + pointers = _pooledPointerArrays[i]; + _pooledPointerArrays.RemoveAt(i); + break; + } + } + } + + if (pointers == null) + { + _createdPointerArrays++; + + if (_createdPointerArrays >= 1024 && !_hasWarnedAboutPointerArrayLeaks) + { + Logging.Warning("Memory leak detected. Are you leaking memory to the GC or are your windows too large? Leaking memory to the GC will cause slowdowns. Make sure all memory is deallocated. [POINTERS ARRAYS]"); + _hasWarnedAboutPointerArrayLeaks = true; + } + + if (_pooledPointerArrays.Count > 0) + { + // Delete this one for expansion. + _pooledPointerArrays.RemoveAt(0); + } + + pointers = new object[allocSize]; + } + else + { + Array.Clear(pointers, 0, pointers.Length); + } + + return pointers; + } - internal static HeapMemory Alloc(uint size) + internal HeapMemory AllocHeapMemory(uint size) { - uint allocSize = Math.Max(minBufferSize, CalculateMultiple(size, bufferMultiple)); + uint allocSize = Math.Max(minHeapMemorySize, CalculateMultiple(size, heapMemorySizeMultiple)); - if (_pooledMemory.Count == 0) + if (_pooledHeapMemory.Count == 0) { - _createdPools++; + _createdHeapMemory++; - if (_createdPools >= 1024 && !_hasWarnedAboutLeak) + if (_createdHeapMemory >= 1024 && !_hasWarnedAboutHeapMemoryLeaks) { - Console.WriteLine("Memory leak detected. Are you leaking memory to the GC or are your windows too large? Leaking memory to the GC will cause slowdowns. Make sure all memory is deallocated."); - _hasWarnedAboutLeak = true; + Logging.Warning("Memory leak detected. Are you leaking memory to the GC or are your windows too large? Leaking memory to the GC will cause slowdowns. Make sure all memory is deallocated. [HEAP MEMORY]"); + _hasWarnedAboutHeapMemoryLeaks = true; } HeapMemory memory = new HeapMemory(allocSize); @@ -52,7 +114,7 @@ internal static HeapMemory Alloc(uint size) } else { - HeapMemory memory = _pooledMemory.Dequeue(); + HeapMemory memory = _pooledHeapMemory.Dequeue(); memory.EnsureSize(allocSize); @@ -64,7 +126,7 @@ internal static HeapMemory Alloc(uint size) } } - internal static void DeAlloc(HeapMemory memory) + internal void DeAlloc(HeapMemory memory) { if (memory.isDead) { @@ -75,7 +137,21 @@ internal static void DeAlloc(HeapMemory memory) memory.VirtualOffset = 0; memory.VirtualCount = 0; - _pooledMemory.Enqueue(memory); + _pooledHeapMemory.Enqueue(memory); + } + + internal void DeAlloc(object[] pointers) + { + if (_configuration.PoolPointerArrays) + { + _pooledPointerArrays.Add(pointers); + } + } + + public void Dispose() + { + _pooledPointerArrays.Clear(); + _pooledPointerArrays.Clear(); } } } diff --git a/Ruffles/Messaging/HeapableSlidingWindow.cs b/Ruffles/Messaging/HeapableSlidingWindow.cs index 18de3a0..12037be 100644 --- a/Ruffles/Messaging/HeapableSlidingWindow.cs +++ b/Ruffles/Messaging/HeapableSlidingWindow.cs @@ -12,7 +12,9 @@ internal class HeapableSlidingWindow where T : IMemoryReleasable private readonly bool _resetOld; private ulong _lastHighestSequence; - public HeapableSlidingWindow(int size, bool resetOld, byte wrapSize) + private readonly MemoryManager _memoryManager; + + public HeapableSlidingWindow(int size, bool resetOld, byte wrapSize, MemoryManager memoryManager) { if (resetOld && size % 8 != 0) { @@ -23,6 +25,8 @@ public HeapableSlidingWindow(int size, bool resetOld, byte wrapSize) _indexes = new int[size]; _wrapSize = wrapSize; _resetOld = resetOld; + + _memoryManager = memoryManager; } public T this[int index] @@ -66,7 +70,7 @@ public void Release() for (int i = 0; i < _array.Length; i++) { _indexes[i] = 0; - _array[i].DeAlloc(); + _array[i].DeAlloc(_memoryManager); _array[i] = default(T); } diff --git a/Ruffles/Messaging/PacketHandler.cs b/Ruffles/Messaging/PacketHandler.cs index 05c5c14..e5a20d8 100644 --- a/Ruffles/Messaging/PacketHandler.cs +++ b/Ruffles/Messaging/PacketHandler.cs @@ -9,7 +9,7 @@ namespace Ruffles.Messaging { internal static class PacketHandler { - internal static void HandleIncomingMessage(ArraySegment payload, Connection connection, SocketConfig activeConfig) + internal static void HandleIncomingMessage(ArraySegment payload, Connection connection, SocketConfig activeConfig, MemoryManager memoryManager) { // This is where all data packets arrive after passing the connection handling. @@ -30,7 +30,7 @@ internal static void HandleIncomingMessage(ArraySegment payload, Connectio if (incomingMessage != null) { // Alloc memory that can be borrowed to userspace - HeapMemory memory = MemoryManager.Alloc((uint)incomingMessage.Value.Count); + HeapMemory memory = memoryManager.AllocHeapMemory((uint)incomingMessage.Value.Count); // Copy payload to borrowed memory Buffer.BlockCopy(incomingMessage.Value.Array, incomingMessage.Value.Offset, memory.Buffer, 0, incomingMessage.Value.Count); @@ -45,7 +45,8 @@ internal static void HandleIncomingMessage(ArraySegment payload, Connectio Data = new ArraySegment(memory.Buffer, (int)memory.VirtualOffset, (int)memory.VirtualCount), InternalMemory = memory, SocketReceiveTime = DateTime.Now, - ChannelId = channelId + ChannelId = channelId, + MemoryManager = memoryManager }); } @@ -69,7 +70,8 @@ internal static void HandleIncomingMessage(ArraySegment payload, Connectio Data = new ArraySegment(messageMemory.Buffer, (int)messageMemory.VirtualOffset, (int)messageMemory.VirtualCount), InternalMemory = messageMemory, SocketReceiveTime = DateTime.Now, - ChannelId = channelId + ChannelId = channelId, + MemoryManager = memoryManager }); } } @@ -77,7 +79,7 @@ internal static void HandleIncomingMessage(ArraySegment payload, Connectio } } - internal static void SendMessage(ArraySegment payload, Connection connection, byte channelId, bool noDelay) + internal static void SendMessage(ArraySegment payload, Connection connection, byte channelId, bool noDelay, MemoryManager memoryManager) { if (channelId < 0 || channelId >= connection.Channels.Length) { @@ -101,7 +103,7 @@ internal static void SendMessage(ArraySegment payload, Connection connecti // DeAlloc the memory again. This is done for unreliable channels that need the message after the initial send. for (int i = 0; i < messageMemory.Length; i++) { - MemoryManager.DeAlloc(messageMemory[i]); + memoryManager.DeAlloc(messageMemory[i]); } } } From 6909e7734740df8fb47403cb108135f678300b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Tue, 3 Sep 2019 11:58:59 +0200 Subject: [PATCH 06/19] Fixed fragment resending --- .../ReliableSequencedFragmentedChannel .cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs b/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs index 4312ed0..ff1f0cd 100644 --- a/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs +++ b/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs @@ -37,6 +37,27 @@ public bool IsAlloced public PendingOutgoingFragment[] Fragments; public bool Alive; + public bool AllFragmentsAlive + { + get + { + if (Fragments == null) + { + return false; + } + + for (int i = 0; i < Fragments.Length; i++) + { + if (!Fragments[i].Alive) + { + return false; + } + } + + return true; + } + } + public void DeAlloc(MemoryManager memoryManager) { if (IsAlloced) @@ -467,7 +488,7 @@ public void HandleAck(ArraySegment payload) } } - for (ushort i = sequence; _sendSequencer[i].Alive; i++) + for (ushort i = sequence; _sendSequencer[i].Alive && _sendSequencer[i].AllFragmentsAlive; i++) { _incomingLowestAckedSequence = i; } @@ -563,7 +584,6 @@ public void InternalUpdate() Sequence = i }; - Console.WriteLine("Resending fragment for sequence: " + i + " and fragment: " + j); connection.SendRaw(new ArraySegment(_sendSequencer[i].Fragments[j].Memory.Buffer, (int)_sendSequencer[i].Fragments[j].Memory.VirtualOffset, (int)_sendSequencer[i].Fragments[j].Memory.VirtualCount), false); } } From 2474e707777e664ef8087dba2d2a98b878b4a08a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Tue, 3 Sep 2019 12:03:37 +0200 Subject: [PATCH 07/19] Added unit tests --- .../Channels/ReliableSequencedChannelTests.cs | 108 ++++++ ...ReliableSequencedFragmentedChannelTests.cs | 319 ++++++++++++++++++ Ruffles.Tests/Helpers/BufferHelper.cs | 16 + Ruffles.Tests/Ruffles.Tests.csproj | 13 + Ruffles.Tests/Stubs/ConnectionStub.cs | 34 ++ Ruffles.Tests/packages.config | 4 + Ruffles.sln | 6 + Ruffles/AssemblyInfo.cs | 3 + .../Channels/ReliableSequencedChannel.cs | 8 +- .../ReliableSequencedFragmentedChannel .cs | 6 +- Ruffles/Connections/Connection.cs | 10 +- Ruffles/Connections/ConnectionBase.cs | 12 + Ruffles/Core/RuffleSocket.cs | 8 +- Ruffles/Ruffles.csproj | 16 - 14 files changed, 529 insertions(+), 34 deletions(-) create mode 100644 Ruffles.Tests/Channels/ReliableSequencedChannelTests.cs create mode 100644 Ruffles.Tests/Channels/ReliableSequencedFragmentedChannelTests.cs create mode 100644 Ruffles.Tests/Helpers/BufferHelper.cs create mode 100644 Ruffles.Tests/Ruffles.Tests.csproj create mode 100644 Ruffles.Tests/Stubs/ConnectionStub.cs create mode 100644 Ruffles.Tests/packages.config create mode 100644 Ruffles/AssemblyInfo.cs create mode 100644 Ruffles/Connections/ConnectionBase.cs diff --git a/Ruffles.Tests/Channels/ReliableSequencedChannelTests.cs b/Ruffles.Tests/Channels/ReliableSequencedChannelTests.cs new file mode 100644 index 0000000..90bd737 --- /dev/null +++ b/Ruffles.Tests/Channels/ReliableSequencedChannelTests.cs @@ -0,0 +1,108 @@ +using NUnit.Framework; +using Ruffles.Channeling.Channels; +using Ruffles.Memory; +using Ruffles.Tests.Helpers; +using Ruffles.Tests.Stubs; +using System; + +namespace Ruffles.Tests.Channels +{ + [TestFixture()] + public class ReliableSequencedChannelTests + { + [Test()] + public void TestSimpleMessage() + { + Configuration.SocketConfig config = new Configuration.SocketConfig(); + + MemoryManager memoryManager = new MemoryManager(config); + + ConnectionStub clientsConnectionToServer = new ConnectionStub(); + ConnectionStub serversConnectionToClient = new ConnectionStub(); + + ReliableSequencedChannel clientChannel = new ReliableSequencedChannel(0, clientsConnectionToServer, null, config, memoryManager); + ReliableSequencedChannel serverChannel = new ReliableSequencedChannel(0, serversConnectionToClient, null, config, memoryManager); + + byte[] message = BufferHelper.GetRandomBuffer(1024); + + HeapMemory messageMemory = clientChannel.CreateOutgoingMessage(new ArraySegment(message, 0, 1024), out bool dealloc)[0]; + ArraySegment? payload = serverChannel.HandleIncomingMessagePoll(new ArraySegment(messageMemory.Buffer, (int)messageMemory.VirtualOffset + 2, (int)messageMemory.VirtualCount - 2), out bool hasMore); + + Assert.NotNull(payload); + Assert.False(hasMore); + + byte[] bytePayload = new byte[payload.Value.Count]; + Array.Copy(payload.Value.Array, payload.Value.Offset, bytePayload, 0, payload.Value.Count); + + Assert.AreEqual(message, bytePayload); + } + + [Test()] + public void TestOutOfOrder() + { + Configuration.SocketConfig config = new Configuration.SocketConfig(); + + MemoryManager memoryManager = new MemoryManager(config); + + ConnectionStub clientsConnectionToServer = new ConnectionStub(); + ConnectionStub serversConnectionToClient = new ConnectionStub(); + + ReliableSequencedChannel clientChannel = new ReliableSequencedChannel(0, clientsConnectionToServer, null, config, memoryManager); + ReliableSequencedChannel serverChannel = new ReliableSequencedChannel(0, serversConnectionToClient, null, config, memoryManager); + + // Create 3 payloads + byte[] message1 = BufferHelper.GetRandomBuffer(1024); + byte[] message2 = BufferHelper.GetRandomBuffer(1024); + byte[] message3 = BufferHelper.GetRandomBuffer(1024); + + // Sequence all payloads as outgoing + HeapMemory message1Memory = clientChannel.CreateOutgoingMessage(new ArraySegment(message1, 0, 1024), out bool dealloc)[0]; + HeapMemory message2Memory = clientChannel.CreateOutgoingMessage(new ArraySegment(message2, 0, 1024), out dealloc)[0]; + HeapMemory message3Memory = clientChannel.CreateOutgoingMessage(new ArraySegment(message3, 0, 1024), out dealloc)[0]; + + // Consume 1st payload + ArraySegment? payload1 = serverChannel.HandleIncomingMessagePoll(new ArraySegment(message1Memory.Buffer, (int)message1Memory.VirtualOffset + 2, (int)message1Memory.VirtualCount - 2), out bool hasMore1); + // Consume 3rd payload + ArraySegment? payload3 = serverChannel.HandleIncomingMessagePoll(new ArraySegment(message3Memory.Buffer, (int)message3Memory.VirtualOffset + 2, (int)message3Memory.VirtualCount - 2), out bool hasMore3); + // Consume 2nd payload + ArraySegment? payload2 = serverChannel.HandleIncomingMessagePoll(new ArraySegment(message2Memory.Buffer, (int)message2Memory.VirtualOffset + 2, (int)message2Memory.VirtualCount - 2), out bool hasMore2); + + HeapMemory pollMemory = serverChannel.HandlePoll(); + + { + Assert.NotNull(payload1); + Assert.False(hasMore1); + + byte[] bytePayload = new byte[payload1.Value.Count]; + Array.Copy(payload1.Value.Array, payload1.Value.Offset, bytePayload, 0, payload1.Value.Count); + + Assert.AreEqual(message1, bytePayload); + } + + { + Assert.Null(payload3); + Assert.False(hasMore3); + } + + { + Assert.NotNull(payload2); + Assert.True(hasMore2); + + byte[] bytePayload = new byte[payload2.Value.Count]; + Array.Copy(payload2.Value.Array, payload2.Value.Offset, bytePayload, 0, payload2.Value.Count); + + Assert.AreEqual(message2, bytePayload); + } + + { + // Check for the third packet + Assert.NotNull(pollMemory); + + byte[] bytePayload = new byte[pollMemory.VirtualCount]; + Array.Copy(pollMemory.Buffer, pollMemory.VirtualOffset, bytePayload, 0, pollMemory.VirtualCount); + + Assert.AreEqual(message3, bytePayload); + } + } + } +} diff --git a/Ruffles.Tests/Channels/ReliableSequencedFragmentedChannelTests.cs b/Ruffles.Tests/Channels/ReliableSequencedFragmentedChannelTests.cs new file mode 100644 index 0000000..923c6ad --- /dev/null +++ b/Ruffles.Tests/Channels/ReliableSequencedFragmentedChannelTests.cs @@ -0,0 +1,319 @@ +using NUnit.Framework; +using Ruffles.Channeling.Channels; +using Ruffles.Memory; +using Ruffles.Tests.Helpers; +using Ruffles.Tests.Stubs; +using System; + +namespace Ruffles.Tests.Channels +{ + [TestFixture()] + public class ReliableSequencedFragmentedChannelTests + { + [Test()] + public void TestSimpleMessageSingleFragment() + { + Configuration.SocketConfig config = new Configuration.SocketConfig(); + + MemoryManager memoryManager = new MemoryManager(config); + + ConnectionStub clientsConnectionToServer = new ConnectionStub(); + ConnectionStub serversConnectionToClient = new ConnectionStub(); + + ReliableSequencedFragmentedChannel clientChannel = new ReliableSequencedFragmentedChannel(0, clientsConnectionToServer, null, config, memoryManager); + ReliableSequencedFragmentedChannel serverChannel = new ReliableSequencedFragmentedChannel(0, serversConnectionToClient, null, config, memoryManager); + + byte[] message = BufferHelper.GetRandomBuffer(1024); + + HeapMemory messageMemory = clientChannel.CreateOutgoingMessage(new ArraySegment(message, 0, 1024), out bool dealloc)[0]; + ArraySegment? payload = serverChannel.HandleIncomingMessagePoll(new ArraySegment(messageMemory.Buffer, (int)messageMemory.VirtualOffset + 2, (int)messageMemory.VirtualCount - 2), out bool hasMore); + + Assert.Null(payload); + Assert.True(hasMore); + + HeapMemory payloadMemory = serverChannel.HandlePoll(); + + Assert.NotNull(payloadMemory); + + byte[] bytePayload = new byte[payloadMemory.VirtualCount]; + Array.Copy(payloadMemory.Buffer, payloadMemory.VirtualOffset, bytePayload, 0, payloadMemory.VirtualCount); + + Assert.AreEqual(message, bytePayload); + } + + [Test()] + public void TestOutOfOrderSingleFragment() + { + Configuration.SocketConfig config = new Configuration.SocketConfig(); + + MemoryManager memoryManager = new MemoryManager(config); + + ConnectionStub clientsConnectionToServer = new ConnectionStub(); + ConnectionStub serversConnectionToClient = new ConnectionStub(); + + ReliableSequencedFragmentedChannel clientChannel = new ReliableSequencedFragmentedChannel(0, clientsConnectionToServer, null, config, memoryManager); + ReliableSequencedFragmentedChannel serverChannel = new ReliableSequencedFragmentedChannel(0, serversConnectionToClient, null, config, memoryManager); + + // Create 3 payloads + byte[] message1 = BufferHelper.GetRandomBuffer(1024); + byte[] message2 = BufferHelper.GetRandomBuffer(1024); + byte[] message3 = BufferHelper.GetRandomBuffer(1024); + + // Sequence all payloads as outgoing + HeapMemory message1Memory = clientChannel.CreateOutgoingMessage(new ArraySegment(message1, 0, 1024), out bool dealloc)[0]; + HeapMemory message2Memory = clientChannel.CreateOutgoingMessage(new ArraySegment(message2, 0, 1024), out dealloc)[0]; + HeapMemory message3Memory = clientChannel.CreateOutgoingMessage(new ArraySegment(message3, 0, 1024), out dealloc)[0]; + + // Consume 1st payload + ArraySegment? payload1 = serverChannel.HandleIncomingMessagePoll(new ArraySegment(message1Memory.Buffer, (int)message1Memory.VirtualOffset + 2, (int)message1Memory.VirtualCount - 2), out bool hasMore1); + // Consume 3rd payload + ArraySegment? payload3 = serverChannel.HandleIncomingMessagePoll(new ArraySegment(message3Memory.Buffer, (int)message3Memory.VirtualOffset + 2, (int)message3Memory.VirtualCount - 2), out bool hasMore3); + // Consume 2nd payload + ArraySegment? payload2 = serverChannel.HandleIncomingMessagePoll(new ArraySegment(message2Memory.Buffer, (int)message2Memory.VirtualOffset + 2, (int)message2Memory.VirtualCount - 2), out bool hasMore2); + + Assert.Null(payload1); + Assert.Null(payload2); + Assert.Null(payload3); + + Assert.True(hasMore1); + Assert.True(hasMore2); + Assert.True(hasMore3); + + HeapMemory poll1 = serverChannel.HandlePoll(); + HeapMemory poll2 = serverChannel.HandlePoll(); + HeapMemory poll3 = serverChannel.HandlePoll(); + + Assert.NotNull(poll1); + Assert.NotNull(poll2); + Assert.NotNull(poll3); + + { + byte[] bytePayload = new byte[poll1.VirtualCount]; + Array.Copy(poll1.Buffer, poll1.VirtualOffset, bytePayload, 0, poll1.VirtualCount); + + Assert.AreEqual(message1, bytePayload); + } + + { + byte[] bytePayload = new byte[poll2.VirtualCount]; + Array.Copy(poll2.Buffer, poll2.VirtualOffset, bytePayload, 0, poll2.VirtualCount); + + Assert.AreEqual(message2, bytePayload); + } + + { + byte[] bytePayload = new byte[poll3.VirtualCount]; + Array.Copy(poll3.Buffer, poll3.VirtualOffset, bytePayload, 0, poll3.VirtualCount); + + Assert.AreEqual(message3, bytePayload); + } + } + + [Test()] + public void TestOutOfFragmentOrderMultiFragment() + { + Configuration.SocketConfig config = new Configuration.SocketConfig(); + + MemoryManager memoryManager = new MemoryManager(config); + + ConnectionStub clientsConnectionToServer = new ConnectionStub(); + ConnectionStub serversConnectionToClient = new ConnectionStub(); + + ReliableSequencedFragmentedChannel clientChannel = new ReliableSequencedFragmentedChannel(0, clientsConnectionToServer, null, config, memoryManager); + ReliableSequencedFragmentedChannel serverChannel = new ReliableSequencedFragmentedChannel(0, serversConnectionToClient, null, config, memoryManager); + + // Create 3 payloads + byte[] message1 = BufferHelper.GetRandomBuffer(1024 * 5); + byte[] message2 = BufferHelper.GetRandomBuffer(1024 * 8); + byte[] message3 = BufferHelper.GetRandomBuffer(1024 * 6); + + // Sequence all payloads as outgoing + HeapMemory[] message1Memory = clientChannel.CreateOutgoingMessage(new ArraySegment(message1, 0, 1024 * 5), out bool dealloc); + HeapMemory[] message2Memory = clientChannel.CreateOutgoingMessage(new ArraySegment(message2, 0, 1024 * 8), out dealloc); + HeapMemory[] message3Memory = clientChannel.CreateOutgoingMessage(new ArraySegment(message3, 0, 1024 * 6), out dealloc); + + // Consume 1st payload all except first fragment + bool[] hasMore1s = new bool[message1Memory.Length]; + ArraySegment?[] payload1s = new ArraySegment?[message1Memory.Length]; + for (int i = 1; i < message1Memory.Length; i++) + { + payload1s[i] = serverChannel.HandleIncomingMessagePoll(new ArraySegment(message1Memory[i].Buffer, (int)message1Memory[i].VirtualOffset + 2, (int)message1Memory[i].VirtualCount - 2), out bool hasMore1); + hasMore1s[i] = hasMore1; + } + // Consume 3rd payload only last fragment + bool[] hasMore3s = new bool[message3Memory.Length]; + ArraySegment?[] payload3s = new ArraySegment?[message3Memory.Length]; + + { + payload3s[payload3s.Length - 1] = serverChannel.HandleIncomingMessagePoll(new ArraySegment(message3Memory[payload3s.Length - 1].Buffer, (int)message3Memory[payload3s.Length - 1].VirtualOffset + 2, (int)message3Memory[payload3s.Length - 1].VirtualCount - 2), out bool hasMore3); + hasMore3s[payload3s.Length - 1] = hasMore3; + } + + // Consume 2nd payload all fragments + bool[] hasMore2s = new bool[message2Memory.Length]; + ArraySegment?[] payload2s = new ArraySegment?[message2Memory.Length]; + for (int i = 0; i < message2Memory.Length; i++) + { + payload2s[i] = serverChannel.HandleIncomingMessagePoll(new ArraySegment(message2Memory[i].Buffer, (int)message2Memory[i].VirtualOffset + 2, (int)message2Memory[i].VirtualCount - 2), out bool hasMore2); + hasMore2s[i] = hasMore2; + } + + { + // Consume 3rd payload all except last fragment (completes the last payload) + for (int i = 0; i < message3Memory.Length - 1; i++) + { + payload3s[i] = serverChannel.HandleIncomingMessagePoll(new ArraySegment(message3Memory[i].Buffer, (int)message3Memory[i].VirtualOffset + 2, (int)message3Memory[i].VirtualCount - 2), out bool hasMore3); + hasMore3s[i] = hasMore3; + } + } + + { + // Consume 1st payload first fragment (completes first payload) + payload1s[0] = serverChannel.HandleIncomingMessagePoll(new ArraySegment(message1Memory[0].Buffer, (int)message1Memory[0].VirtualOffset + 2, (int)message1Memory[0].VirtualCount - 2), out bool hasMore1); + hasMore1s[0] = hasMore1; + } + + for (int i = 0; i < payload1s.Length; i++) + { + Assert.Null(payload1s[i]); + } + + for (int i = 0; i < payload3s.Length; i++) + { + Assert.Null(payload3s[i]); + } + + for (int i = 0; i < payload2s.Length; i++) + { + Assert.Null(payload2s[i]); + } + + // TODO: Assert HasMore states + + HeapMemory poll1 = serverChannel.HandlePoll(); + HeapMemory poll2 = serverChannel.HandlePoll(); + HeapMemory poll3 = serverChannel.HandlePoll(); + + Assert.NotNull(poll1, "p1"); + Assert.NotNull(poll2, "p2"); + Assert.NotNull(poll3, "p3"); + + { + byte[] bytePayload = new byte[poll1.VirtualCount]; + Array.Copy(poll1.Buffer, poll1.VirtualOffset, bytePayload, 0, poll1.VirtualCount); + + Assert.AreEqual(message1, bytePayload); + } + + { + byte[] bytePayload = new byte[poll2.VirtualCount]; + Array.Copy(poll2.Buffer, poll2.VirtualOffset, bytePayload, 0, poll2.VirtualCount); + + Assert.AreEqual(message2, bytePayload); + } + + { + byte[] bytePayload = new byte[poll3.VirtualCount]; + Array.Copy(poll3.Buffer, poll3.VirtualOffset, bytePayload, 0, poll3.VirtualCount); + + Assert.AreEqual(message3, bytePayload); + } + } + + [Test()] + public void TestOutOfPacketOrderMultiFragment() + { + Configuration.SocketConfig config = new Configuration.SocketConfig(); + + MemoryManager memoryManager = new MemoryManager(config); + + ConnectionStub clientsConnectionToServer = new ConnectionStub(); + ConnectionStub serversConnectionToClient = new ConnectionStub(); + + ReliableSequencedFragmentedChannel clientChannel = new ReliableSequencedFragmentedChannel(0, clientsConnectionToServer, null, config, memoryManager); + ReliableSequencedFragmentedChannel serverChannel = new ReliableSequencedFragmentedChannel(0, serversConnectionToClient, null, config, memoryManager); + + // Create 3 payloads + byte[] message1 = BufferHelper.GetRandomBuffer(1024 * 5); + byte[] message2 = BufferHelper.GetRandomBuffer(1024 * 8); + byte[] message3 = BufferHelper.GetRandomBuffer(1024 * 6); + + // Sequence all payloads as outgoing + HeapMemory[] message1Memory = clientChannel.CreateOutgoingMessage(new ArraySegment(message1, 0, 1024 * 5), out bool dealloc); + HeapMemory[] message2Memory = clientChannel.CreateOutgoingMessage(new ArraySegment(message2, 0, 1024 * 8), out dealloc); + HeapMemory[] message3Memory = clientChannel.CreateOutgoingMessage(new ArraySegment(message3, 0, 1024 * 6), out dealloc); + + // Consume 1st payload + bool[] hasMore1s = new bool[message1Memory.Length]; + ArraySegment?[] payload1s = new ArraySegment?[message1Memory.Length]; + for (int i = 0; i < message1Memory.Length; i++) + { + payload1s[i] = serverChannel.HandleIncomingMessagePoll(new ArraySegment(message1Memory[i].Buffer, (int)message1Memory[i].VirtualOffset + 2, (int)message1Memory[i].VirtualCount - 2), out bool hasMore1); + hasMore1s[i] = hasMore1; + } + // Consume 3rd payload + bool[] hasMore3s = new bool[message3Memory.Length]; + ArraySegment?[] payload3s = new ArraySegment?[message3Memory.Length]; + for (int i = 0; i < message3Memory.Length; i++) + { + payload3s[i] = serverChannel.HandleIncomingMessagePoll(new ArraySegment(message3Memory[i].Buffer, (int)message3Memory[i].VirtualOffset + 2, (int)message3Memory[i].VirtualCount - 2), out bool hasMore3); + hasMore3s[i] = hasMore3; + } + // Consume 2nd payload + bool[] hasMore2s = new bool[message2Memory.Length]; + ArraySegment?[] payload2s = new ArraySegment?[message2Memory.Length]; + for (int i = 0; i < message2Memory.Length; i++) + { + payload2s[i] = serverChannel.HandleIncomingMessagePoll(new ArraySegment(message2Memory[i].Buffer, (int)message2Memory[i].VirtualOffset + 2, (int)message2Memory[i].VirtualCount - 2), out bool hasMore2); + hasMore2s[i] = hasMore2; + } + + + for (int i = 0; i < payload1s.Length; i++) + { + Assert.Null(payload1s[i]); + } + + for (int i = 0; i < payload3s.Length; i++) + { + Assert.Null(payload3s[i]); + } + + for (int i = 0; i < payload2s.Length; i++) + { + Assert.Null(payload2s[i]); + } + + // TODO: Assert HasMore states + + HeapMemory poll1 = serverChannel.HandlePoll(); + HeapMemory poll2 = serverChannel.HandlePoll(); + HeapMemory poll3 = serverChannel.HandlePoll(); + + Assert.NotNull(poll1); + Assert.NotNull(poll2); + Assert.NotNull(poll3); + + { + byte[] bytePayload = new byte[poll1.VirtualCount]; + Array.Copy(poll1.Buffer, poll1.VirtualOffset, bytePayload, 0, poll1.VirtualCount); + + Assert.AreEqual(message1, bytePayload); + } + + { + byte[] bytePayload = new byte[poll2.VirtualCount]; + Array.Copy(poll2.Buffer, poll2.VirtualOffset, bytePayload, 0, poll2.VirtualCount); + + Assert.AreEqual(message2, bytePayload); + } + + { + byte[] bytePayload = new byte[poll3.VirtualCount]; + Array.Copy(poll3.Buffer, poll3.VirtualOffset, bytePayload, 0, poll3.VirtualCount); + + Assert.AreEqual(message3, bytePayload); + } + } + } +} diff --git a/Ruffles.Tests/Helpers/BufferHelper.cs b/Ruffles.Tests/Helpers/BufferHelper.cs new file mode 100644 index 0000000..761fcc5 --- /dev/null +++ b/Ruffles.Tests/Helpers/BufferHelper.cs @@ -0,0 +1,16 @@ +namespace Ruffles.Tests.Helpers +{ + public static class BufferHelper + { + public static byte[] GetRandomBuffer(int size) + { + System.Random rnd = new System.Random(); + + byte[] buffer = new byte[size]; + + rnd.NextBytes(buffer); + + return buffer; + } + } +} diff --git a/Ruffles.Tests/Ruffles.Tests.csproj b/Ruffles.Tests/Ruffles.Tests.csproj new file mode 100644 index 0000000..9475087 --- /dev/null +++ b/Ruffles.Tests/Ruffles.Tests.csproj @@ -0,0 +1,13 @@ + + + Ruffles.Tests + net35;net471;net45;netstandard2.0 + 7 + + + + + + + + \ No newline at end of file diff --git a/Ruffles.Tests/Stubs/ConnectionStub.cs b/Ruffles.Tests/Stubs/ConnectionStub.cs new file mode 100644 index 0000000..99e1e5b --- /dev/null +++ b/Ruffles.Tests/Stubs/ConnectionStub.cs @@ -0,0 +1,34 @@ +using System; +using Ruffles.Connections; + +namespace Ruffles.Tests.Stubs +{ + public class ConnectionStub : ConnectionBase + { + public override double Roundtrip { get; internal set; } = 10; + + public event Action> OnSendData; + public event Action OnDisconnect; + + internal override void AddRoundtripSample(ulong sample) + { + + } + + internal override void Disconnect(bool sendMessage) + { + if (OnDisconnect != null) + { + OnDisconnect(); + } + } + + internal override void SendRaw(ArraySegment payload, bool noMerge) + { + if (OnSendData != null) + { + OnSendData(payload); + } + } + } +} diff --git a/Ruffles.Tests/packages.config b/Ruffles.Tests/packages.config new file mode 100644 index 0000000..ded08cf --- /dev/null +++ b/Ruffles.Tests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Ruffles.sln b/Ruffles.sln index 8772f6b..152e747 100644 --- a/Ruffles.sln +++ b/Ruffles.sln @@ -5,6 +5,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ruffles", "Ruffles\Ruffles. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ruffles.Example", "Ruffles.Example\Ruffles.Example.csproj", "{A7D6CF7D-7E6E-4E2B-B5AE-63A4EBCC177E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ruffles.Tests", "Ruffles.Tests\Ruffles.Tests.csproj", "{AA689AA8-3A9C-4830-9BB5-CD8CE7A24D14}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -19,5 +21,9 @@ Global {A7D6CF7D-7E6E-4E2B-B5AE-63A4EBCC177E}.Debug|Any CPU.Build.0 = Debug|Any CPU {A7D6CF7D-7E6E-4E2B-B5AE-63A4EBCC177E}.Release|Any CPU.ActiveCfg = Release|Any CPU {A7D6CF7D-7E6E-4E2B-B5AE-63A4EBCC177E}.Release|Any CPU.Build.0 = Release|Any CPU + {AA689AA8-3A9C-4830-9BB5-CD8CE7A24D14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA689AA8-3A9C-4830-9BB5-CD8CE7A24D14}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA689AA8-3A9C-4830-9BB5-CD8CE7A24D14}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA689AA8-3A9C-4830-9BB5-CD8CE7A24D14}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Ruffles/AssemblyInfo.cs b/Ruffles/AssemblyInfo.cs new file mode 100644 index 0000000..2a90fba --- /dev/null +++ b/Ruffles/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Ruffles.Tests")] \ No newline at end of file diff --git a/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs b/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs index d5a9cbf..222ec27 100644 --- a/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs +++ b/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs @@ -56,16 +56,14 @@ public void DeAlloc(MemoryManager memoryManager) // Channel info private readonly byte channelId; - private readonly Connection connection; - private readonly RuffleSocket socket; + private readonly ConnectionBase connection; private readonly SocketConfig config; private readonly MemoryManager memoryManager; - internal ReliableSequencedChannel(byte channelId, Connection connection, RuffleSocket socket, SocketConfig config, MemoryManager memoryManager) + internal ReliableSequencedChannel(byte channelId, ConnectionBase connection, SocketConfig config, MemoryManager memoryManager) { this.channelId = channelId; this.connection = connection; - this.socket = socket; this.config = config; this.memoryManager = memoryManager; @@ -101,7 +99,7 @@ public HeapMemory HandlePoll() { // Read the sequence number ushort sequence = (ushort)(payload.Array[payload.Offset] | (ushort)(payload.Array[payload.Offset + 1] << 8)); - + if (SequencingUtils.Distance(sequence, _incomingLowestAckedSequence, sizeof(ushort)) <= 0 || _receiveSequencer[sequence].Alive) { // We have already acked this message. Ack again diff --git a/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs b/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs index ff1f0cd..2529788 100644 --- a/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs +++ b/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs @@ -192,16 +192,14 @@ public void DeAlloc(MemoryManager memoryManager) // Channel info private readonly byte channelId; - private readonly Connection connection; - private readonly RuffleSocket socket; + private readonly ConnectionBase connection; private readonly SocketConfig config; private readonly MemoryManager memoryManager; - internal ReliableSequencedFragmentedChannel(byte channelId, Connection connection, RuffleSocket socket, SocketConfig config, MemoryManager memoryManager) + internal ReliableSequencedFragmentedChannel(byte channelId, ConnectionBase connection, SocketConfig config, MemoryManager memoryManager) { this.channelId = channelId; this.connection = connection; - this.socket = socket; this.config = config; this.memoryManager = memoryManager; diff --git a/Ruffles/Connections/Connection.cs b/Ruffles/Connections/Connection.cs index a53b321..7bd07b3 100644 --- a/Ruffles/Connections/Connection.cs +++ b/Ruffles/Connections/Connection.cs @@ -12,7 +12,7 @@ namespace Ruffles.Connections /// /// A connection between two RuffleSockets. /// - public class Connection + public class Connection : ConnectionBase { /// /// Gets the id of the connection. This is reused for connections by the RuffleSocket. @@ -63,7 +63,7 @@ public class Connection /// Gets the estimated roundtrip. /// /// The estimated roundtrip. - public double Roundtrip { get; internal set; } = 10; + public override double Roundtrip { get; internal set; } = 10; internal readonly UnreliableSequencedChannel HeartbeatChannel; internal MessageMerger Merger; internal IChannel[] Channels; @@ -88,17 +88,17 @@ internal Connection(SocketConfig config, MemoryManager memoryManager) } } - internal void SendRaw(ArraySegment payload, bool noMerge) + internal override void SendRaw(ArraySegment payload, bool noMerge) { Socket.SendRaw(this, payload, noMerge); } - internal void Disconnect(bool sendMessage) + internal override void Disconnect(bool sendMessage) { Socket.DisconnectConnection(this, sendMessage, false); } - internal void AddRoundtripSample(ulong sample) + internal override void AddRoundtripSample(ulong sample) { double rttDistance = sample - Roundtrip; Roundtrip += (rttDistance * 0.1d); diff --git a/Ruffles/Connections/ConnectionBase.cs b/Ruffles/Connections/ConnectionBase.cs new file mode 100644 index 0000000..4ca371d --- /dev/null +++ b/Ruffles/Connections/ConnectionBase.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ruffles.Connections +{ + public abstract class ConnectionBase + { + internal abstract void SendRaw(ArraySegment payload, bool noMerge); + internal abstract void Disconnect(bool sendMessage); + internal abstract void AddRoundtripSample(ulong sample); + public abstract double Roundtrip { get; internal set; } + } +} diff --git a/Ruffles/Core/RuffleSocket.cs b/Ruffles/Core/RuffleSocket.cs index f12bf01..f5dddc3 100644 --- a/Ruffles/Core/RuffleSocket.cs +++ b/Ruffles/Core/RuffleSocket.cs @@ -1029,7 +1029,7 @@ internal void HandlePacket(ArraySegment payload, EndPoint endpoint) break; case ChannelType.ReliableSequenced: { - pendingConnection.Channels[i] = new ReliableSequencedChannel(i, pendingConnection, this, config, memoryManager); + pendingConnection.Channels[i] = new ReliableSequencedChannel(i, pendingConnection, config, memoryManager); } break; case ChannelType.UnreliableRaw: @@ -1039,7 +1039,7 @@ internal void HandlePacket(ArraySegment payload, EndPoint endpoint) break; case ChannelType.ReliableSequencedFragmented: { - pendingConnection.Channels[i] = new ReliableSequencedFragmentedChannel(i, pendingConnection, this, config, memoryManager); + pendingConnection.Channels[i] = new ReliableSequencedFragmentedChannel(i, pendingConnection, config, memoryManager); } break; default: @@ -1358,7 +1358,7 @@ internal Connection AddNewConnection(EndPoint endpoint, ConnectionState state) break; case ChannelType.ReliableSequenced: { - connection.Channels[x] = new ReliableSequencedChannel(x, connection, this, config, memoryManager); + connection.Channels[x] = new ReliableSequencedChannel(x, connection, config, memoryManager); } break; case ChannelType.UnreliableRaw: @@ -1368,7 +1368,7 @@ internal Connection AddNewConnection(EndPoint endpoint, ConnectionState state) break; case ChannelType.ReliableSequencedFragmented: { - connection.Channels[x] = new ReliableSequencedFragmentedChannel(x, connection, this, config, memoryManager); + connection.Channels[x] = new ReliableSequencedFragmentedChannel(x, connection, config, memoryManager); } break; default: diff --git a/Ruffles/Ruffles.csproj b/Ruffles/Ruffles.csproj index 09b38ec..126d505 100644 --- a/Ruffles/Ruffles.csproj +++ b/Ruffles/Ruffles.csproj @@ -1,21 +1,5 @@ - net35;net471;net45;netstandard2.0;netcoreapp2.0 - - - - - - - - - - - - - - - From c62031f30ebaf57ed492bacb82dd8d8397a81e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Tue, 3 Sep 2019 13:04:29 +0200 Subject: [PATCH 08/19] Adjusted defaults and added validation to SocketConfig --- .../Channeling/Channels/ReliableChannel.cs | 8 ++++ .../Channels/ReliableSequencedChannel.cs | 8 ++++ .../ReliableSequencedFragmentedChannel .cs | 21 ++++++++-- .../Channeling/Channels/UnreliableChannel.cs | 8 ++++ .../Channels/UnreliableRawChannel.cs | 8 ++++ .../Channels/UnreliableSequencedChannel.cs | 13 ++++++- Ruffles/Configuration/SocketConfig.cs | 39 ++++++++++++++++--- Ruffles/Connections/Connection.cs | 2 +- Ruffles/Core/RuffleSocket.cs | 13 +++++-- 9 files changed, 107 insertions(+), 13 deletions(-) diff --git a/Ruffles/Channeling/Channels/ReliableChannel.cs b/Ruffles/Channeling/Channels/ReliableChannel.cs index 7652da1..487e619 100644 --- a/Ruffles/Channeling/Channels/ReliableChannel.cs +++ b/Ruffles/Channeling/Channels/ReliableChannel.cs @@ -4,6 +4,7 @@ using Ruffles.Connections; using Ruffles.Memory; using Ruffles.Messaging; +using Ruffles.Utils; namespace Ruffles.Channeling.Channels { @@ -111,6 +112,13 @@ public HeapMemory HandlePoll() public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { + if (payload.Count > config.MaxMessageSize) + { + Logging.Error("Tried to send message that was too large. Use a fragmented channel instead. [Size=" + payload.Count + "] [MaxMessageSize=" + config.MaxFragments + "]"); + dealloc = false; + return null; + } + // Increment the sequence number _lastOutboundSequenceNumber++; diff --git a/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs b/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs index 222ec27..54ba2f3 100644 --- a/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs +++ b/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs @@ -4,6 +4,7 @@ using Ruffles.Core; using Ruffles.Memory; using Ruffles.Messaging; +using Ruffles.Utils; namespace Ruffles.Channeling.Channels { @@ -151,6 +152,13 @@ public HeapMemory HandlePoll() public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { + if (payload.Count > config.MaxMessageSize) + { + Logging.Error("Tried to send message that was too large. Use a fragmented channel instead. [Size=" + payload.Count + "] [MaxMessageSize=" + config.MaxFragments + "]"); + dealloc = false; + return null; + } + // Increment the sequence number _lastOutboundSequenceNumber++; diff --git a/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs b/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs index 2529788..fb9f7cc 100644 --- a/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs +++ b/Ruffles/Channeling/Channels/ReliableSequencedFragmentedChannel .cs @@ -4,6 +4,7 @@ using Ruffles.Core; using Ruffles.Memory; using Ruffles.Messaging; +using Ruffles.Utils; namespace Ruffles.Channeling.Channels { @@ -266,6 +267,13 @@ public HeapMemory HandlePoll() // IsFinal is the most significant bit bool isFinal = (ushort)((encodedFragment & 32768) >> 15) == 1; + if (fragment > config.MaxFragments) + { + Logging.Error("FragmentId was too large. [FragmentId=" + fragment + "] [Config.MaxFragments=" + config.MaxFragments + "]. The fragment was silently dropped, expect a timeout."); + hasMore = false; + return null; + } + if (SequencingUtils.Distance(sequence, _incomingLowestAckedSequence, sizeof(ushort)) <= 0 || (_receiveSequencer[sequence].Alive && _receiveSequencer[sequence].IsComplete) || (_receiveSequencer[sequence].Alive && _receiveSequencer[sequence].Fragments.Array != null && _receiveSequencer[sequence].Fragments.Count > fragment && _receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment] != null && !((HeapMemory)_receiveSequencer[sequence].Fragments.Array[_receiveSequencer[sequence].Fragments.Offset + fragment]).isDead)) @@ -364,12 +372,19 @@ public HeapMemory HandlePoll() public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { - // Increment the sequence number - _lastOutboundSequenceNumber++; - // Calculate the amount of fragments required int fragmentsRequired = (payload.Count + (config.MaxMessageSize - 1)) / config.MaxMessageSize; + if (fragmentsRequired > config.MaxFragments) + { + Logging.Error("Tried to create message that was too large. [Size=" + payload.Count + "] [FragmentsRequired=" + fragmentsRequired + "] [Config.MaxFragments=" + config.MaxFragments + "]"); + dealloc = false; + return null; + } + + // Increment the sequence number + _lastOutboundSequenceNumber++; + // Alloc array HeapMemory[] fragments = new HeapMemory[fragmentsRequired]; diff --git a/Ruffles/Channeling/Channels/UnreliableChannel.cs b/Ruffles/Channeling/Channels/UnreliableChannel.cs index a2330b0..a1064b0 100644 --- a/Ruffles/Channeling/Channels/UnreliableChannel.cs +++ b/Ruffles/Channeling/Channels/UnreliableChannel.cs @@ -3,6 +3,7 @@ using Ruffles.Connections; using Ruffles.Memory; using Ruffles.Messaging; +using Ruffles.Utils; namespace Ruffles.Channeling.Channels { @@ -34,6 +35,13 @@ internal UnreliableChannel(byte channelId, Connection connection, SocketConfig c public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { + if (payload.Count > config.MaxMessageSize) + { + Logging.Error("Tried to send message that was too large. Use a fragmented channel instead. [Size=" + payload.Count + "] [MaxMessageSize=" + config.MaxFragments + "]"); + dealloc = false; + return null; + } + // Increment the sequence number _lastOutboundSequenceNumber++; diff --git a/Ruffles/Channeling/Channels/UnreliableRawChannel.cs b/Ruffles/Channeling/Channels/UnreliableRawChannel.cs index a9aa4e1..bd8ef2a 100644 --- a/Ruffles/Channeling/Channels/UnreliableRawChannel.cs +++ b/Ruffles/Channeling/Channels/UnreliableRawChannel.cs @@ -3,6 +3,7 @@ using Ruffles.Connections; using Ruffles.Memory; using Ruffles.Messaging; +using Ruffles.Utils; namespace Ruffles.Channeling.Channels { @@ -26,6 +27,13 @@ internal UnreliableRawChannel(byte channelId, Connection connection, SocketConfi public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { + if (payload.Count > config.MaxMessageSize) + { + Logging.Error("Tried to send message that was too large. Use a fragmented channel instead. [Size=" + payload.Count + "] [MaxMessageSize=" + config.MaxFragments + "]"); + dealloc = false; + return null; + } + // Allocate the memory HeapMemory memory = memoryManager.AllocHeapMemory((uint)payload.Count + 2); diff --git a/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs b/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs index 96184cc..6757ce0 100644 --- a/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs +++ b/Ruffles/Channeling/Channels/UnreliableSequencedChannel.cs @@ -1,7 +1,9 @@ using System; +using Ruffles.Configuration; using Ruffles.Connections; using Ruffles.Memory; using Ruffles.Messaging; +using Ruffles.Utils; namespace Ruffles.Channeling.Channels { @@ -17,18 +19,27 @@ internal class UnreliableSequencedChannel : IChannel private readonly byte channelId; private readonly Connection connection; private readonly MemoryManager memoryManager; + private readonly SocketConfig config; - internal UnreliableSequencedChannel(byte channelId, Connection connection, MemoryManager memoryManager) + internal UnreliableSequencedChannel(byte channelId, Connection connection, SocketConfig config, MemoryManager memoryManager) { this.channelId = channelId; this.connection = connection; this.memoryManager = memoryManager; + this.config = config; } private readonly HeapMemory[] SINGLE_MESSAGE_ARRAY = new HeapMemory[1]; public HeapMemory[] CreateOutgoingMessage(ArraySegment payload, out bool dealloc) { + if (payload.Count > config.MaxMessageSize) + { + Logging.Error("Tried to send message that was too large. Use a fragmented channel instead. [Size=" + payload.Count + "] [MaxMessageSize=" + config.MaxFragments + "]"); + dealloc = false; + return null; + } + // Increment the sequence number _lastOutboundSequenceNumber++; diff --git a/Ruffles/Configuration/SocketConfig.cs b/Ruffles/Configuration/SocketConfig.cs index 553741e..a5fc828 100644 --- a/Ruffles/Configuration/SocketConfig.cs +++ b/Ruffles/Configuration/SocketConfig.cs @@ -1,4 +1,5 @@ -using System.Net; +using System.Collections.Generic; +using System.Net; using Ruffles.Channeling; using Ruffles.Simulation; @@ -39,23 +40,34 @@ public class SocketConfig /// If this is enabled, all connections has to be manually recycled by the user after receiving the disconnect or timeout events. /// public bool ReuseConnections = true; - - public bool PoolPointerArrays = false; + /// + /// Whether or not to pool pointer arrays. This is benefitial if your application is garbage critical. + /// + public bool PoolPointerArrays = true; // Bandwidth /// /// The maximum size of a merged packet. /// Increasing this increases the memory usage for each connection. /// - public ushort MaxMergeMessageSize = 256; + public ushort MaxMergeMessageSize = 1450; /// /// The maximum delay before merged packets are sent. /// public ulong MaxMergeDelay = 250; // Fragmentation - public ushort MaxMessageSize = 1400; + /// + /// The maximum user size of a single message. + /// + public ushort MaxMessageSize = 1450; + /// + /// The default size of fragment arrays. + /// public ushort FragmentArrayBaseSize = 64; + /// + /// The maximum amount of fragments allowed to be used. + /// public ushort MaxFragments = 32768; // Memory @@ -190,5 +202,22 @@ public class SocketConfig /// Whether or not packet merging should be enabled. /// public bool EnablePacketMerging = true; + + public List GetInvalidConfiguration() + { + List messages = new List(); + + if (MaxFragments > 32768) + { + messages.Add("MaxFragments cannot be greater than 2^15=32768"); + } + + if (MaxMergeMessageSize > MaxMessageSize) + { + messages.Add("MaxMergeMessageSize cannot be greater than MaxMessageSize"); + } + + return messages; + } } } diff --git a/Ruffles/Connections/Connection.cs b/Ruffles/Connections/Connection.cs index 7bd07b3..c5b501a 100644 --- a/Ruffles/Connections/Connection.cs +++ b/Ruffles/Connections/Connection.cs @@ -84,7 +84,7 @@ internal Connection(SocketConfig config, MemoryManager memoryManager) { if (config.EnableHeartbeats) { - HeartbeatChannel = new UnreliableSequencedChannel(0, this, memoryManager); + HeartbeatChannel = new UnreliableSequencedChannel(0, this, config, memoryManager); } } diff --git a/Ruffles/Core/RuffleSocket.cs b/Ruffles/Core/RuffleSocket.cs index f5dddc3..781f6a2 100644 --- a/Ruffles/Core/RuffleSocket.cs +++ b/Ruffles/Core/RuffleSocket.cs @@ -46,7 +46,14 @@ public class RuffleSocket private readonly MemoryManager memoryManager; public RuffleSocket(SocketConfig config) - { + { + List configurationErrors = config.GetInvalidConfiguration(); + + if (configurationErrors.Count > 0) + { + Logging.Error("Invalid configuration! Please fix the following issues [" + string.Join(",", configurationErrors.ToArray()) + "]"); + } + this.config = config; if (config.UseSimulator) @@ -1024,7 +1031,7 @@ internal void HandlePacket(ArraySegment payload, EndPoint endpoint) break; case ChannelType.UnreliableSequenced: { - pendingConnection.Channels[i] = new UnreliableSequencedChannel(i, pendingConnection, memoryManager); + pendingConnection.Channels[i] = new UnreliableSequencedChannel(i, pendingConnection, config, memoryManager); } break; case ChannelType.ReliableSequenced: @@ -1353,7 +1360,7 @@ internal Connection AddNewConnection(EndPoint endpoint, ConnectionState state) break; case ChannelType.UnreliableSequenced: { - connection.Channels[x] = new UnreliableSequencedChannel(x, connection, memoryManager); + connection.Channels[x] = new UnreliableSequencedChannel(x, connection, config, memoryManager); } break; case ChannelType.ReliableSequenced: From be6eca7cb612780fb9f273dd37f3c14cda692c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Tue, 3 Sep 2019 13:35:28 +0200 Subject: [PATCH 09/19] Updated readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ea8999..fccca9a 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ There are currently a few ways of sending messages in Ruffles. The types are: All messages are guaranteed to be delivered, the order is not guaranteed, duplicates are dropped. Uses a fixed sliding window. #### ReliableSequenced All messages are guaranteed to be delivered with the order also being guaranteed, duplicates are dropped. Uses a fixed sliding window. +#### ReliableSequencedFragmented +All messages are guaranteed to be delivered with the order also being guaranteed, duplicates are dropped. Uses a fixed sliding window. Allows large messages to be fragmented. #### Unreliable Delivery is not guaranteed, nor is the order. Duplicates are dropped. #### UnreliableSequenced @@ -63,9 +65,8 @@ Small packets will be delayed for sending, this allows them to be merged into on This is stuff I want to and plan to add * Path MTU -* Fragmentation +* More Fragmentation Types * Explicit Nack -* Layer 4 DOS Prevention with an on-connect HashCash challenge * Reliable StateUpdate / LastPacket channel * MLAPI.Relay Support * MLAPI.NAT (Holepuncher) support From c12620bf968547e1ae74efe44767f9a785700bf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Tue, 3 Sep 2019 14:11:20 +0200 Subject: [PATCH 10/19] Removed old comment --- Ruffles/Messaging/PacketHandler.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Ruffles/Messaging/PacketHandler.cs b/Ruffles/Messaging/PacketHandler.cs index e5a20d8..3040b3e 100644 --- a/Ruffles/Messaging/PacketHandler.cs +++ b/Ruffles/Messaging/PacketHandler.cs @@ -13,10 +13,6 @@ internal static void HandleIncomingMessage(ArraySegment payload, Connectio { // This is where all data packets arrive after passing the connection handling. - // TODO: - // 1. Fragmentation - // 2. Delay to pack messages - byte channelId = payload.Array[payload.Offset]; if (channelId < 0 || channelId >= connection.Channels.Length) @@ -100,7 +96,7 @@ internal static void SendMessage(ArraySegment payload, Connection connecti if (dealloc) { - // DeAlloc the memory again. This is done for unreliable channels that need the message after the initial send. + // DeAlloc the memory again. This is done for unreliable channels that dont need the message after the initial send. for (int i = 0; i < messageMemory.Length; i++) { memoryManager.DeAlloc(messageMemory[i]); From 6b0e8bf5310bfcaa8c80386cf8da77e059f6c49f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Tue, 3 Sep 2019 14:12:33 +0200 Subject: [PATCH 11/19] Added print when an invalid channel got a message --- Ruffles/Messaging/PacketHandler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Ruffles/Messaging/PacketHandler.cs b/Ruffles/Messaging/PacketHandler.cs index 3040b3e..9f89961 100644 --- a/Ruffles/Messaging/PacketHandler.cs +++ b/Ruffles/Messaging/PacketHandler.cs @@ -4,6 +4,7 @@ using Ruffles.Connections; using Ruffles.Core; using Ruffles.Memory; +using Ruffles.Utils; namespace Ruffles.Messaging { @@ -18,6 +19,7 @@ internal static void HandleIncomingMessage(ArraySegment payload, Connectio if (channelId < 0 || channelId >= connection.Channels.Length) { // ChannelId out of range + Logging.Error("Got message on channel out of range. [ChannelId=" + channelId + "]"); return; } From 731cdba93379c2ff7169e11306efe7904c4dbf59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Tue, 3 Sep 2019 14:43:11 +0200 Subject: [PATCH 12/19] fix: Fixes hashcash difficulty --- Ruffles/Core/RuffleSocket.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Ruffles/Core/RuffleSocket.cs b/Ruffles/Core/RuffleSocket.cs index 781f6a2..9ba82e6 100644 --- a/Ruffles/Core/RuffleSocket.cs +++ b/Ruffles/Core/RuffleSocket.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Net; using System.Net.Sockets; using Ruffles.Channeling; @@ -255,7 +255,7 @@ public Connection Connect(EndPoint endpoint) // Increment counter counter++; } - while ((hash << (sizeof(ulong) - config.ChallengeDifficulty)) >> (sizeof(ulong) - config.ChallengeDifficulty) != 0); + while ((hash << (sizeof(ulong) * 8 - config.ChallengeDifficulty)) >> (sizeof(ulong) * 8 - config.ChallengeDifficulty) != 0); // Make counter 1 less counter--; @@ -721,7 +721,7 @@ internal void HandlePacket(ArraySegment payload, EndPoint endpoint) ulong claimedHash = HashProvider.GetStableHash64(challengeUnixTime, counter, userIv); // Check if the hash collides - bool isCollided = ((claimedHash << (sizeof(ulong) - config.ChallengeDifficulty)) >> (sizeof(ulong) - config.ChallengeDifficulty)) == 0; + bool isCollided = ((claimedHash << (sizeof(ulong) * 8 - config.ChallengeDifficulty)) >> (sizeof(ulong) * 8 - config.ChallengeDifficulty)) == 0; if (!isCollided) { From e1a290089ae2ce510f72a8ac438694e0e13ef9c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Tue, 3 Sep 2019 14:43:58 +0200 Subject: [PATCH 13/19] Updated socketConfig to use more reasonable defaults --- Ruffles/Configuration/SocketConfig.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Ruffles/Configuration/SocketConfig.cs b/Ruffles/Configuration/SocketConfig.cs index a5fc828..e39773c 100644 --- a/Ruffles/Configuration/SocketConfig.cs +++ b/Ruffles/Configuration/SocketConfig.cs @@ -68,13 +68,13 @@ public class SocketConfig /// /// The maximum amount of fragments allowed to be used. /// - public ushort MaxFragments = 32768; + public ushort MaxFragments = 512; // Memory /// /// The maxmimum packet size. Should be larger than the MTU. /// - public ushort MaxBufferSize = ushort.MaxValue; + public ushort MaxBufferSize = 1500; /// /// The maximum amount of connections. Increasing this increases the memory impact. /// @@ -112,7 +112,7 @@ public class SocketConfig /// /// The difficulty of the challenge in bits. Higher difficulties exponentially increase the solve time. /// - public byte ChallengeDifficulty = 10; + public byte ChallengeDifficulty = 20; /// /// The amount of successfull initialization vectors to keep for initial connection requests. /// @@ -120,7 +120,7 @@ public class SocketConfig /// /// The connection request challenge time window in seconds. /// - public ulong ConnectionChallengeTimeWindow = 30; + public ulong ConnectionChallengeTimeWindow = 60 * 5; /// /// Whether or not to enable time based connection challenge. /// Enabling this will prevent slot filling attacks but requires the connector and connection receivers times to be synced with a diff of From b184b3254a520764bd8d6de5e3f7a341d61716e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Tue, 3 Sep 2019 14:44:39 +0200 Subject: [PATCH 14/19] Reduced example difficulty --- Ruffles.Example/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Ruffles.Example/Program.cs b/Ruffles.Example/Program.cs index 851160f..90f5043 100644 --- a/Ruffles.Example/Program.cs +++ b/Ruffles.Example/Program.cs @@ -11,7 +11,7 @@ public class Program { internal static readonly SocketConfig ServerConfig = new SocketConfig() { - ChallengeDifficulty = 25, // Difficulty 25 is fairly hard + ChallengeDifficulty = 20, // Difficulty 20 is fairly hard ChannelTypes = new ChannelType[] { ChannelType.Reliable, @@ -32,7 +32,7 @@ public class Program internal static readonly SocketConfig ClientConfig = new SocketConfig() { - ChallengeDifficulty = 25, // Difficulty 25 is fairly hard + ChallengeDifficulty = 20, // Difficulty 20 is fairly hard DualListenPort = 0, // Port 0 means we get a port by the operating system SimulatorConfig = new Simulation.SimulatorConfig() { From eb295f2102f36123ad5306ce12c70db175a25ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Tue, 3 Sep 2019 15:08:35 +0200 Subject: [PATCH 15/19] feat: Added unconnected messages --- README.md | 2 + Ruffles/Configuration/SocketConfig.cs | 6 ++- Ruffles/Core/NetworkEventType.cs | 6 ++- Ruffles/Core/RuffleSocket.cs | 71 +++++++++++++++++++++++++++ Ruffles/Messaging/MessageType.cs | 3 +- 5 files changed, 85 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fccca9a..fe21cae 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ Delivery is not guaranteed, nor is the order. Duplicates are dropped. Delivery is not guaranteed but the order is. Older packets and duplicate packets are dropped. #### UnreliableRaw Delivery is not guaranteed nor is the order. Duplicates are not dropped. +#### UnconnectedMessages +Raw UDP packets that does not require a connection. ### Threading Ruffles can run in many different threading environments, it can be run passively single threaded, actively single threaded, or in a threaded environment where everything is done via message queues while remaining garbage free. diff --git a/Ruffles/Configuration/SocketConfig.cs b/Ruffles/Configuration/SocketConfig.cs index e39773c..ce4dc05 100644 --- a/Ruffles/Configuration/SocketConfig.cs +++ b/Ruffles/Configuration/SocketConfig.cs @@ -23,7 +23,11 @@ public class SocketConfig /// /// Whether or not the socket will listen on IPv4 and IPv6 in dual mode on the same port. /// - public bool UseIPv6Dual = true; + public bool UseIPv6Dual = true; + /// + /// Whether or not unconnected messages should be allowed. + /// + public bool AllowUnconnectedMessages = false; // Performance /// diff --git a/Ruffles/Core/NetworkEventType.cs b/Ruffles/Core/NetworkEventType.cs index 14ac131..c62963d 100644 --- a/Ruffles/Core/NetworkEventType.cs +++ b/Ruffles/Core/NetworkEventType.cs @@ -24,6 +24,10 @@ public enum NetworkEventType /// /// A connection sent data. /// - Data + Data, + /// + /// An endpoint sent unconnected data. + /// + UnconnectedData } } diff --git a/Ruffles/Core/RuffleSocket.cs b/Ruffles/Core/RuffleSocket.cs index 9ba82e6..8ead1cb 100644 --- a/Ruffles/Core/RuffleSocket.cs +++ b/Ruffles/Core/RuffleSocket.cs @@ -207,6 +207,35 @@ public void Send(ArraySegment payload, ulong connectionId, byte channelId, } Send(payload, Connections[connectionId], channelId, noDelay); + } + + /// + /// Sends an unconnected message. + /// + /// Payload. + /// Endpoint. + public void SendUnconnected(ArraySegment payload, IPEndPoint endpoint) + { + if (payload.Count > config.MaxMessageSize) + { + Logging.Error("Tried to send unconnected message that was too large. [Size=" + payload.Count + "] [MaxMessageSize=" + config.MaxFragments + "]"); + return; + } + + // Allocate the memory + HeapMemory memory = memoryManager.AllocHeapMemory((uint)payload.Count + 4); + + // Write headers + memory.Buffer[0] = HeaderPacker.Pack((byte)MessageType.UnconnectedData, false); + + // Copy payload to borrowed memory + Buffer.BlockCopy(payload.Array, payload.Offset, memory.Buffer, 1, payload.Count); + + // Send the packet + SendRawRealEndPoint(endpoint, payload); + + // Release memory + memoryManager.DeAlloc(memory); } /// @@ -611,6 +640,18 @@ private void SendRawReal(Connection connection, ArraySegment payload) { int sent = ipv6Socket.SendTo(payload.Array, payload.Offset, payload.Count, SocketFlags.None, connection.EndPoint); } + } + + private void SendRawRealEndPoint(IPEndPoint endpoint, ArraySegment payload) + { + if (endpoint.AddressFamily == AddressFamily.InterNetwork) + { + int sent = ipv4Socket.SendTo(payload.Array, payload.Offset, payload.Count, SocketFlags.None, endpoint); + } + else if (endpoint.AddressFamily == AddressFamily.InterNetworkV6) + { + int sent = ipv6Socket.SendTo(payload.Array, payload.Offset, payload.Count, SocketFlags.None, endpoint); + } } internal void HandlePacket(ArraySegment payload, EndPoint endpoint) @@ -1189,6 +1230,36 @@ internal void HandlePacket(ArraySegment payload, EndPoint endpoint) } } break; + case (byte)MessageType.UnconnectedData: + { + if (config.AllowUnconnectedMessages) + { + // Alloc memory that can be borrowed to userspace + HeapMemory memory = memoryManager.AllocHeapMemory((uint)payload.Count - 1); + + // Copy payload to borrowed memory + Buffer.BlockCopy(payload.Array, payload.Offset + 1, memory.Buffer, 0, payload.Count - 1); + + // Send to userspace + UserEventQueue.Enqueue(new NetworkEvent() + { + Connection = null, + Socket = this, + Type = NetworkEventType.UnconnectedData, + AllowUserRecycle = true, + Data = new ArraySegment(memory.Buffer, (int)memory.VirtualOffset, (int)memory.VirtualCount), + InternalMemory = memory, + SocketReceiveTime = DateTime.Now, + ChannelId = 0, + MemoryManager = memoryManager + }); + } + else + { + Logging.Warning("Got unconnected message but SocketConfig.AllowUnconnectedMessages is disabled."); + } + } + break; } } diff --git a/Ruffles/Messaging/MessageType.cs b/Ruffles/Messaging/MessageType.cs index a0c2309..c167f70 100644 --- a/Ruffles/Messaging/MessageType.cs +++ b/Ruffles/Messaging/MessageType.cs @@ -11,6 +11,7 @@ internal enum MessageType : byte Data, Disconnect, Ack, - Merge + Merge, + UnconnectedData } } From a34d895cccd1c16c0d7d8679cdefa08672f4ae1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Tue, 3 Sep 2019 18:15:46 +0200 Subject: [PATCH 16/19] feat: Added bit-acks --- .../Channels/ReliableSequencedChannel.cs | 46 ++++++++++++++++++- Ruffles/Configuration/SocketConfig.cs | 4 +- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs b/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs index 54ba2f3..1cb26af 100644 --- a/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs +++ b/Ruffles/Channeling/Channels/ReliableSequencedChannel.cs @@ -201,6 +201,33 @@ public void HandleAck(ArraySegment payload) // Read the sequence number ushort sequence = (ushort)(payload.Array[payload.Offset] | (ushort)(payload.Array[payload.Offset + 1] << 8)); + // Handle the base ack + HandleAck(sequence); + + if ((payload.Count - 2) > 0) + { + // There is more data. This has to be ack bits + + // Calculate the amount of ack bits + int bits = (payload.Count - 2) * 8; + + // Iterate ack bits + for (byte i = 0; i < bits; i++) + { + // Get the ack for the current bit + bool isAcked = ((payload.Array[payload.Offset + 2 + (i / 8)] & ((byte)Math.Pow(2, (7 - (i % 8))))) >> (7 - (i % 8))) == 1; + + if (isAcked) + { + // Handle the bit ack + HandleAck((ushort)(sequence - i)); + } + } + } + } + + private void HandleAck(ushort sequence) + { if (_sendSequencer[sequence].Alive) { // Dealloc the memory held by the sequencer for the packet @@ -242,7 +269,7 @@ public void Reset() private void SendAck(ushort sequence) { // Alloc ack memory - HeapMemory ackMemory = memoryManager.AllocHeapMemory(4); + HeapMemory ackMemory = memoryManager.AllocHeapMemory(4 + (uint)(config.EnableMergedAcks ? config.MergedAckBytes : 0)); // Write header ackMemory.Buffer[0] = HeaderPacker.Pack((byte)MessageType.Ack, false); @@ -252,8 +279,23 @@ private void SendAck(ushort sequence) ackMemory.Buffer[2] = (byte)sequence; ackMemory.Buffer[3] = (byte)(sequence >> 8); + if (config.EnableMergedAcks) + { + // Reset the memory + for (int i = 0; i < config.MergedAckBytes; i++) + { + ackMemory.Buffer[4 + i] = 0; + } + + // Set the bit fields + for (byte i = 0; i < config.MergedAckBytes * 8; i++) + { + ackMemory.Buffer[(i / 8)] |= (byte)(((SequencingUtils.Distance(((ushort)(sequence - (i + 1))), _incomingLowestAckedSequence, sizeof(ushort)) <= 0 || _receiveSequencer[((ushort)(sequence - (i + 1)))].Alive) ? 1 : 0) << (7 - (i % 8))); + } + } + // Send ack - connection.SendRaw(new ArraySegment(ackMemory.Buffer, 0, 4), false); + connection.SendRaw(new ArraySegment(ackMemory.Buffer, 0, 4 + (config.EnableMergedAcks ? config.MergedAckBytes : 0)), false); // Return memory memoryManager.DeAlloc(ackMemory); diff --git a/Ruffles/Configuration/SocketConfig.cs b/Ruffles/Configuration/SocketConfig.cs index ce4dc05..384d8dc 100644 --- a/Ruffles/Configuration/SocketConfig.cs +++ b/Ruffles/Configuration/SocketConfig.cs @@ -58,7 +58,9 @@ public class SocketConfig /// /// The maximum delay before merged packets are sent. /// - public ulong MaxMergeDelay = 250; + public ulong MaxMergeDelay = 250; + public bool EnableMergedAcks = true; + public byte MergedAckBytes = 8; // Fragmentation /// From 2de1afd5e266143bef71fc7a0461012d4c9d30c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Tue, 3 Sep 2019 19:25:40 +0200 Subject: [PATCH 17/19] feat: Added bit-ack to ReliableChannel --- .../Channeling/Channels/ReliableChannel.cs | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/Ruffles/Channeling/Channels/ReliableChannel.cs b/Ruffles/Channeling/Channels/ReliableChannel.cs index 487e619..66458f3 100644 --- a/Ruffles/Channeling/Channels/ReliableChannel.cs +++ b/Ruffles/Channeling/Channels/ReliableChannel.cs @@ -161,6 +161,33 @@ public void HandleAck(ArraySegment payload) // Read the sequence number ushort sequence = (ushort)(payload.Array[payload.Offset] | (ushort)(payload.Array[payload.Offset + 1] << 8)); + // Handle the base ack + HandleAck(sequence); + + if ((payload.Count - 2) > 0) + { + // There is more data. This has to be ack bits + + // Calculate the amount of ack bits + int bits = (payload.Count - 2) * 8; + + // Iterate ack bits + for (byte i = 0; i < bits; i++) + { + // Get the ack for the current bit + bool isAcked = ((payload.Array[payload.Offset + 2 + (i / 8)] & ((byte)Math.Pow(2, (7 - (i % 8))))) >> (7 - (i % 8))) == 1; + + if (isAcked) + { + // Handle the bit ack + HandleAck((ushort)(sequence - i)); + } + } + } + } + + private void HandleAck(ushort sequence) + { if (_sendSequencer[sequence].Alive) { // Dealloc the memory held by the sequencer @@ -233,7 +260,7 @@ public void Reset() private void SendAck(ushort sequence) { // Alloc ack memory - HeapMemory ackMemory = memoryManager.AllocHeapMemory(4); + HeapMemory ackMemory = memoryManager.AllocHeapMemory(4 + (uint)(config.EnableMergedAcks ? config.MergedAckBytes : 0)); // Write header ackMemory.Buffer[0] = HeaderPacker.Pack((byte)MessageType.Ack, false); @@ -243,8 +270,23 @@ private void SendAck(ushort sequence) ackMemory.Buffer[2] = (byte)sequence; ackMemory.Buffer[3] = (byte)(sequence >> 8); + if (config.EnableMergedAcks) + { + // Reset the memory + for (int i = 0; i < config.MergedAckBytes; i++) + { + ackMemory.Buffer[4 + i] = 0; + } + + // Set the bit fields + for (byte i = 0; i < config.MergedAckBytes * 8; i++) + { + ackMemory.Buffer[(i / 8)] |= (byte)(((SequencingUtils.Distance(((ushort)(sequence - (i + 1))), _incomingLowestAckedSequence, sizeof(ushort)) <= 0 || _incomingAckedPackets.Contains(((ushort)(sequence - (i + 1))))) ? 1 : 0) << (7 - (i % 8))); + } + } + // Send ack - connection.SendRaw(new ArraySegment(ackMemory.Buffer, 0, 4), false); + connection.SendRaw(new ArraySegment(ackMemory.Buffer, 0, 4 + (config.EnableMergedAcks ? config.MergedAckBytes : 0)), false); // Return memory memoryManager.DeAlloc(ackMemory); From cfbc15d415e035aa0733213e5086d9d977e0b3d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Tue, 3 Sep 2019 19:37:36 +0200 Subject: [PATCH 18/19] refactor: Removed duplicate configs --- Ruffles/Configuration/SocketConfig.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Ruffles/Configuration/SocketConfig.cs b/Ruffles/Configuration/SocketConfig.cs index 0cea955..384d8dc 100644 --- a/Ruffles/Configuration/SocketConfig.cs +++ b/Ruffles/Configuration/SocketConfig.cs @@ -76,20 +76,6 @@ public class SocketConfig /// public ushort MaxFragments = 512; - // Fragmentation - /// - /// The maximum user size of a single message. - /// - public ushort MaxMessageSize = 1450; - /// - /// The default size of fragment arrays. - /// - public ushort FragmentArrayBaseSize = 64; - /// - /// The maximum amount of fragments allowed to be used. - /// - public ushort MaxFragments = 512; - // Memory /// /// The maxmimum packet size. Should be larger than the MTU. From af2d5f31c0926d77415c63ab759e1d92d1a1566a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Tue, 3 Sep 2019 20:25:44 +0200 Subject: [PATCH 19/19] doc(xml): Added xml documentation for config --- Ruffles/Configuration/SocketConfig.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Ruffles/Configuration/SocketConfig.cs b/Ruffles/Configuration/SocketConfig.cs index 384d8dc..54393b3 100644 --- a/Ruffles/Configuration/SocketConfig.cs +++ b/Ruffles/Configuration/SocketConfig.cs @@ -59,7 +59,13 @@ public class SocketConfig /// The maximum delay before merged packets are sent. /// public ulong MaxMergeDelay = 250; + /// + /// Whether or not to enable merged acks for non fragmented channels. + /// public bool EnableMergedAcks = true; + /// + /// The amount of bytes to use for merged acks. + /// public byte MergedAckBytes = 8; // Fragmentation