diff --git a/Hazel.UnitTests/Reliability/PacketDropTests.cs b/Hazel.UnitTests/Reliability/PacketDropTests.cs new file mode 100644 index 0000000..4f60c26 --- /dev/null +++ b/Hazel.UnitTests/Reliability/PacketDropTests.cs @@ -0,0 +1,85 @@ +using Hazel.Udp; +using Hazel.Udp.FewerThreads; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Diagnostics; +using System.Net; +using System.Threading; + +namespace Hazel.UnitTests.Reliability +{ + [TestClass] + public class PacketDropTests + { + // This test fails because even at 10% packet drop and 10ms + [TestMethod] + public void SustainedPacketLossShouldBeFine() + { + var serverEp = new IPEndPoint(IPAddress.Loopback, 23432); + var clientEp = new IPEndPoint(IPAddress.Loopback, 23433); + + var logger = new ConsoleLogger(true); + + using (SocketCapture capture = new UnreliableSocketCapture(clientEp, serverEp, logger)) + using (ThreadLimitedUdpConnectionListener server = new ThreadLimitedUdpConnectionListener(4, serverEp, logger)) + using (UnityUdpClientConnection client = new UnityUdpClientConnection(logger, clientEp)) + using (Timer timer = new Timer(_ => + { + var up = Stopwatch.StartNew(); + var cnt = client.FixedUpdate(); + if (cnt != 0) + { + logger.WriteInfo($"Took {up.ElapsedMilliseconds}ms to resend {cnt} pkts"); + } + }, null, 100, 100)) + { + server.ReliableResendPollRateMs = 10; + UdpConnection serverClient = null; + server.NewConnection += (evt) => serverClient = (UdpConnection)evt.Connection; + + server.Start(); + client.Connect(); + + var msg = MessageWriter.Get(SendOption.Reliable); + msg.Length = 500; + for (int i = 0; i < 100; ++i) + { + client.Send(msg); + // client.FixedUpdate(); + Thread.Sleep(1000 / 30); + } + + while (serverClient.Statistics.ReliableMessagesReceived < 101) + { + Assert.AreEqual(ConnectionState.Connected, client.State); + // client.FixedUpdate(); + Thread.Sleep(1000 / 30); + } + + Thread.Sleep(2000); + + Assert.AreEqual(serverClient.Statistics.ReliableMessagesReceived, client.Statistics.ReliableMessagesSent); + Assert.IsTrue(6 < client.Statistics.MessagesResent); + Assert.IsTrue(1 > client.AveragePingMs, "Ping was kinda high: " + client.AveragePingMs); + + msg.Recycle(); + } + } + + private class UnreliableSocketCapture : SocketCapture + { + private Random r = new Random(10); + + public UnreliableSocketCapture(IPEndPoint captureEndpoint, IPEndPoint remoteEndPoint, ILogger logger = null) + : base(captureEndpoint, remoteEndPoint, logger) + { + } + + protected override bool ShouldSendToRemote() + { + // 10% drop rate + return r.NextDouble() > .1f; + } + } + } +} diff --git a/Hazel.UnitTests/StressTests.cs b/Hazel.UnitTests/Reliability/StressTests.cs similarity index 90% rename from Hazel.UnitTests/StressTests.cs rename to Hazel.UnitTests/Reliability/StressTests.cs index c92b0b7..8bbeb82 100644 --- a/Hazel.UnitTests/StressTests.cs +++ b/Hazel.UnitTests/Reliability/StressTests.cs @@ -9,7 +9,7 @@ using Hazel.Udp.FewerThreads; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Hazel.UnitTests +namespace Hazel.UnitTests.Reliability { [TestClass] public class StressTests @@ -22,13 +22,14 @@ public void StressTestOpeningConnections() var ep = new IPEndPoint(IPAddress.Loopback, 22023); Parallel.For(0, 10000, new ParallelOptions { MaxDegreeOfParallelism = 64 }, - (i) => { - - var connection = new UdpClientConnection(new TestLogger(), ep); - connection.KeepAliveInterval = 50; + (i) => + { + + var connection = new UdpClientConnection(new TestLogger(), ep); + connection.KeepAliveInterval = 50; - connection.Connect(new byte[5]); - }); + connection.Connect(new byte[5]); + }); } // This was a thing that happened to us a DDoS. Mildly instructional that we straight up ignore it. diff --git a/Hazel.UnitTests/SocketCapture.cs b/Hazel.UnitTests/Utils/SocketCapture.cs similarity index 93% rename from Hazel.UnitTests/SocketCapture.cs rename to Hazel.UnitTests/Utils/SocketCapture.cs index 584d08c..6264026 100644 --- a/Hazel.UnitTests/SocketCapture.cs +++ b/Hazel.UnitTests/Utils/SocketCapture.cs @@ -142,6 +142,8 @@ private void ReceiveLoop() } } + protected virtual bool ShouldSendToRemote() => true; + private void SendToRemoteLoop() { while (!this.cancellationToken.IsCancellationRequested) @@ -156,8 +158,15 @@ private void SendToRemoteLoop() if (this.forRemote.TryTake(out var packet)) { - this.logger.WriteInfo($"Passed 1 packet of {packet.Length} bytes to remote"); - this.captureSocket.SendTo(packet.GetUnderlyingArray(), packet.Offset, packet.Length, SocketFlags.None, this.remoteEndPoint); + if (ShouldSendToRemote()) + { + // this.logger.WriteInfo($"Passed 1 packet of {packet.Length} bytes to remote"); + this.captureSocket.SendTo(packet.GetUnderlyingArray(), packet.Offset, packet.Length, SocketFlags.None, this.remoteEndPoint); + } + else + { + this.logger.WriteInfo($"Dropped 1 packet of {packet.Length} bytes to remote"); + } } } } @@ -176,7 +185,7 @@ private void SendToLocalLoop() if (this.forLocal.TryTake(out var packet)) { - this.logger.WriteInfo($"Passed 1 packet of {packet.Length} bytes to local"); + // this.logger.WriteInfo($"Passed 1 packet of {packet.Length} bytes to local"); this.captureSocket.SendTo(packet.GetUnderlyingArray(), packet.Offset, packet.Length, SocketFlags.None, this.localEndPoint); } } diff --git a/Hazel.UnitTests/TestHelper.cs b/Hazel.UnitTests/Utils/TestHelper.cs similarity index 100% rename from Hazel.UnitTests/TestHelper.cs rename to Hazel.UnitTests/Utils/TestHelper.cs diff --git a/Hazel.UnitTests/TestLogger.cs b/Hazel.UnitTests/Utils/TestLogger.cs similarity index 100% rename from Hazel.UnitTests/TestLogger.cs rename to Hazel.UnitTests/Utils/TestLogger.cs diff --git a/Hazel.UnitTests/Utils.cs b/Hazel.UnitTests/Utils/Utils.cs similarity index 100% rename from Hazel.UnitTests/Utils.cs rename to Hazel.UnitTests/Utils/Utils.cs diff --git a/Hazel/ConnectionStatistics.cs b/Hazel/ConnectionStatistics.cs index f2c3ed9..cee80dc 100644 --- a/Hazel/ConnectionStatistics.cs +++ b/Hazel/ConnectionStatistics.cs @@ -82,17 +82,7 @@ public int UnreliableMessagesSent /// each time that LogReliableSend is called by the Connection. Messages that caused an error are not /// counted and messages are only counted once all other operations in the send are complete. /// - public int ReliableMessagesSent - { - get - { - return reliableMessagesSent; - } - } - - /// - /// The number of unreliable messages sent. - /// + public int ReliableMessagesSent => reliableMessagesSent; int reliableMessagesSent; /// @@ -103,17 +93,7 @@ public int ReliableMessagesSent /// each time that LogFragmentedSend is called by the Connection. Messages that caused an error are not /// counted and messages are only counted once all other operations in the send are complete. /// - public int FragmentedMessagesSent - { - get - { - return fragmentedMessagesSent; - } - } - - /// - /// The number of fragmented messages sent. - /// + public int FragmentedMessagesSent => fragmentedMessagesSent; int fragmentedMessagesSent; /// @@ -124,17 +104,7 @@ public int FragmentedMessagesSent /// each time that LogAcknowledgementSend is called by the Connection. Messages that caused an error are not /// counted and messages are only counted once all other operations in the send are complete. /// - public int AcknowledgementMessagesSent - { - get - { - return acknowledgementMessagesSent; - } - } - - /// - /// The number of acknowledgement messages sent. - /// + public int AcknowledgementMessagesSent => acknowledgementMessagesSent; int acknowledgementMessagesSent; /// @@ -145,17 +115,7 @@ public int AcknowledgementMessagesSent /// each time that LogHelloSend is called by the Connection. Messages that caused an error are not /// counted and messages are only counted once all other operations in the send are complete. /// - public int HelloMessagesSent - { - get - { - return helloMessagesSent; - } - } - - /// - /// The number of hello messages sent. - /// + public int HelloMessagesSent => helloMessagesSent; int helloMessagesSent; /// @@ -171,17 +131,7 @@ public int HelloMessagesSent /// For the number of bytes including protocol bytes see . /// /// - public long DataBytesSent - { - get - { - return Interlocked.Read(ref dataBytesSent); - } - } - - /// - /// The number of bytes of data sent. - /// + public long DataBytesSent => Interlocked.Read(ref dataBytesSent); long dataBytesSent; /// @@ -198,17 +148,7 @@ public long DataBytesSent /// For the number of data bytes excluding protocol bytes see . /// /// - public long TotalBytesSent - { - get - { - return Interlocked.Read(ref totalBytesSent); - } - } - - /// - /// The number of bytes sent in total. - /// + public long TotalBytesSent => Interlocked.Read(ref totalBytesSent); long totalBytesSent; /// @@ -216,10 +156,12 @@ public long TotalBytesSent /// public int MessagesReceived { - get - { - return UnreliableMessagesReceived + ReliableMessagesReceived + FragmentedMessagesReceived + AcknowledgementMessagesReceived + helloMessagesReceived; - } + get => UnreliableMessagesReceived + + ReliableMessagesReceived + + FragmentedMessagesReceived + + AcknowledgementMessagesReceived + + HelloMessagesReceived + + PingMessagesReceived; } /// @@ -229,17 +171,7 @@ public int MessagesReceived /// This is the number of unreliable messages that were received by the , incremented /// each time that LogUnreliableReceive is called by the Connection. Messages are counted before the receive event is invoked. /// - public int UnreliableMessagesReceived - { - get - { - return unreliableMessagesReceived; - } - } - - /// - /// The number of unreliable messages received. - /// + public int UnreliableMessagesReceived => unreliableMessagesReceived; int unreliableMessagesReceived; /// @@ -249,17 +181,7 @@ public int UnreliableMessagesReceived /// This is the number of reliable messages that were received by the , incremented /// each time that LogReliableReceive is called by the Connection. Messages are counted before the receive event is invoked. /// - public int ReliableMessagesReceived - { - get - { - return reliableMessagesReceived; - } - } - - /// - /// The number of reliable messages received. - /// + public int ReliableMessagesReceived => reliableMessagesReceived; int reliableMessagesReceived; /// @@ -269,17 +191,7 @@ public int ReliableMessagesReceived /// This is the number of fragmented messages that were received by the , incremented /// each time that LogFragmentedReceive is called by the Connection. Messages are counted before the receive event is invoked. /// - public int FragmentedMessagesReceived - { - get - { - return fragmentedMessagesReceived; - } - } - - /// - /// The number of fragmented messages received. - /// + public int FragmentedMessagesReceived => fragmentedMessagesReceived; int fragmentedMessagesReceived; /// @@ -289,38 +201,21 @@ public int FragmentedMessagesReceived /// This is the number of acknowledgement messages that were received by the , incremented /// each time that LogAcknowledgemntReceive is called by the Connection. Messages are counted before the receive event is invoked. /// - public int AcknowledgementMessagesReceived - { - get - { - return acknowledgementMessagesReceived; - } - } - - /// - /// The number of acknowledgement messages received. - /// + public int AcknowledgementMessagesReceived => acknowledgementMessagesReceived; int acknowledgementMessagesReceived; /// /// The number of ping messages received. /// - /// - /// This is the number of hello messages that were received by the , incremented - /// each time that LogHelloReceive is called by the Connection. Messages are counted before the receive event is invoked. - /// - public int PingMessagesReceived - { - get - { - return pingMessagesReceived; - } - } + public int PingMessagesReceived => pingMessagesReceived; + private int pingMessagesReceived; + /// - /// The number of hello messages received. + /// The number of ping messages sent. /// - int pingMessagesReceived; + public int PingMessagesSent => pingMessagesSent; + private int pingMessagesSent; /// /// The number of hello messages received. @@ -329,17 +224,7 @@ public int PingMessagesReceived /// This is the number of hello messages that were received by the , incremented /// each time that LogHelloReceive is called by the Connection. Messages are counted before the receive event is invoked. /// - public int HelloMessagesReceived - { - get - { - return helloMessagesReceived; - } - } - - /// - /// The number of hello messages received. - /// + public int HelloMessagesReceived => helloMessagesReceived; int helloMessagesReceived; /// @@ -355,17 +240,7 @@ public int HelloMessagesReceived /// For the number of bytes including protocol bytes see . /// /// - public long DataBytesReceived - { - get - { - return Interlocked.Read(ref dataBytesReceived); - } - } - - /// - /// The number of bytes of data received. - /// + public long DataBytesReceived => Interlocked.Read(ref dataBytesReceived); long dataBytesReceived; /// @@ -381,20 +256,13 @@ public long DataBytesReceived /// For the number of data bytes excluding protocol bytes see . /// /// - public long TotalBytesReceived - { - get - { - return Interlocked.Read(ref totalBytesReceived); - } - } + public long TotalBytesReceived => Interlocked.Read(ref totalBytesReceived); + long totalBytesReceived; /// - /// The number of bytes received in total. + /// Number of reliable messages resent /// - long totalBytesReceived; - - public int MessagesResent { get { return messagesResent; } } + public int MessagesResent => messagesResent; int messagesResent; /// @@ -540,6 +408,19 @@ internal void LogReliablePacketAcknowledged() Interlocked.Increment(ref this.reliablePacketsAcknowledged); } + /// + /// Logs the sending of a ping packet in the statistics. + /// + /// The total number of bytes received. + /// + /// This should be called before the received event is invoked so it is up to date for subscribers to that event. + /// + internal void LogPingSend(int totalLength) + { + Interlocked.Increment(ref pingMessagesSent); + Interlocked.Add(ref totalBytesSent, totalLength); + } + /// /// Logs the receiving of a hello data packet in the statistics. /// @@ -563,6 +444,7 @@ internal void LogPingReceive(int totalLength) internal void LogHelloReceive(int totalLength) { Interlocked.Increment(ref helloMessagesReceived); + Interlocked.Increment(ref reliableMessagesReceived); Interlocked.Add(ref totalBytesReceived, totalLength); } diff --git a/Hazel/FewerThreads/ThreadLimitedUdpConnectionListener.cs b/Hazel/FewerThreads/ThreadLimitedUdpConnectionListener.cs index 7eaa6c8..e9b8db5 100644 --- a/Hazel/FewerThreads/ThreadLimitedUdpConnectionListener.cs +++ b/Hazel/FewerThreads/ThreadLimitedUdpConnectionListener.cs @@ -28,6 +28,11 @@ private struct ReceiveMessageInfo private const int SendReceiveBufferSize = 1024 * 1024; + /// + /// How frequently sent reliable messages are checked for needing resend. + /// + public int ReliableResendPollRateMs = 100; + private Socket socket; protected ILogger Logger; @@ -154,7 +159,7 @@ private void ManageReliablePackets() sock.ManageReliablePackets(); } - Thread.Sleep(100); + Thread.Sleep(this.ReliableResendPollRateMs); } } diff --git a/Hazel/Udp/UdpConnection.KeepAlive.cs b/Hazel/Udp/UdpConnection.KeepAlive.cs index 07eacc9..d63b707 100644 --- a/Hazel/Udp/UdpConnection.KeepAlive.cs +++ b/Hazel/Udp/UdpConnection.KeepAlive.cs @@ -129,7 +129,7 @@ private void SendPing() ping.Stopwatch.Restart(); WriteBytesToConnection(buffer, buffer.Length); - Statistics.LogReliableSend(0); + Statistics.LogPingSend(buffer.Length); } /// diff --git a/Hazel/Udp/UdpConnection.Reliable.cs b/Hazel/Udp/UdpConnection.Reliable.cs index dfa538d..aad1452 100644 --- a/Hazel/Udp/UdpConnection.Reliable.cs +++ b/Hazel/Udp/UdpConnection.Reliable.cs @@ -8,9 +8,20 @@ namespace Hazel.Udp { partial class UdpConnection { - private const int MinResendDelayMs = 50; - private const int MaxInitialResendDelayMs = 300; - private const int MaxAdditionalResendDelayMs = 1000; + /// + /// The minimum delay to resend a packet for the first time. Even if times is less. + /// + public int MinResendDelayMs = 50; + + /// + /// The maximum delay to resend a packet for the first time. Even if times is more. + /// + public int MaxInitialResendDelayMs = 300; + + /// + /// The maximum delay to resend a packet after the first resend. + /// + public int MaxAdditionalResendDelayMs = 1000; public readonly ObjectPool PacketPool; @@ -19,12 +30,12 @@ partial class UdpConnection /// /// /// - /// For reliable delivery data is resent at specified intervals unless an acknowledgement is received from the + /// Reliable messages are resent at specified intervals unless an acknowledgement is received from the /// receiving device. The ResendTimeout specifies the interval between the packets being resent, each time a packet /// is resent the interval is increased for that packet until the duration exceeds the value. /// /// - /// Setting this to its default of 0 will mean the timeout is 2 times the value of the average ping, usually + /// Setting this to its default of 0 will mean the timeout is times the value of the average ping, usually /// resulting in a more dynamic resend that responds to endpoints on slower or faster connections. /// /// @@ -70,7 +81,7 @@ partial class UdpConnection /// This returns the average ping for a one-way trip as calculated from the reliable packets that have been sent /// and acknowledged by the endpoint. /// - private float _pingMs = 500; + private float _pingMs = 100; /// /// The maximum times a message should be resent before marking the endpoint as disconnected. @@ -89,11 +100,13 @@ partial class UdpConnection public class Packet : IRecyclable { public ushort Id; + + private ILogger logger; private SmartBuffer Data; private readonly UdpConnection Connection; private int Length; - public int NextTimeoutMs; + private int NextTimeoutMs; private volatile bool Acknowledged; public Action AckCallback; @@ -106,9 +119,10 @@ internal Packet(UdpConnection connection) this.Connection = connection; } - internal void Set(ushort id, SmartBuffer data, int length, int timeout, Action ackCallback) + internal void Set(ushort id, ILogger logger, SmartBuffer data, int length, int timeout, Action ackCallback) { this.Id = id; + this.logger = logger; this.Data = data; this.Data.AddUsage(); @@ -123,17 +137,42 @@ internal void Set(ushort id, SmartBuffer data, int length, int timeout, Action a } // Packets resent - public int Resend() + public int Resend(bool force = false) { + if (this.Acknowledged) + { + return 0; + } + var connection = this.Connection; - if (!this.Acknowledged && connection != null) + int lifetimeMs = (int)this.Stopwatch.ElapsedMilliseconds; + if (lifetimeMs >= connection.DisconnectTimeoutMs) { - long lifetimeMs = this.Stopwatch.ElapsedMilliseconds; - if (lifetimeMs >= connection.DisconnectTimeoutMs) + if (connection.reliableDataPacketsSent.TryRemove(this.Id, out Packet self)) + { + connection.DisconnectInternal(HazelInternalErrors.ReliablePacketWithoutResponse, $"Reliable packet {self.Id} (size={this.Length}) was not ack'd after {lifetimeMs}ms ({self.Retransmissions} resends)"); + + self.Recycle(); + } + + return 0; + } + + if (force || lifetimeMs >= this.NextTimeoutMs) + { + // Enforce 10 ms min resend delay + if (this.NextTimeoutMs > lifetimeMs + 10) + { + return 0; + } + + ++this.Retransmissions; + if (connection.ResendLimit != 0 + && this.Retransmissions > connection.ResendLimit) { if (connection.reliableDataPacketsSent.TryRemove(this.Id, out Packet self)) { - connection.DisconnectInternal(HazelInternalErrors.ReliablePacketWithoutResponse, $"Reliable packet {self.Id} (size={this.Length}) was not ack'd after {lifetimeMs}ms ({self.Retransmissions} resends)"); + connection.DisconnectInternal(HazelInternalErrors.ReliablePacketWithoutResponse, $"Reliable packet {self.Id} (size={this.Length}) was not ack'd after {self.Retransmissions} resends ({lifetimeMs}ms)"); self.Recycle(); } @@ -141,33 +180,25 @@ public int Resend() return 0; } - if (lifetimeMs >= this.NextTimeoutMs) - { - ++this.Retransmissions; - if (connection.ResendLimit != 0 - && this.Retransmissions > connection.ResendLimit) - { - if (connection.reliableDataPacketsSent.TryRemove(this.Id, out Packet self)) - { - connection.DisconnectInternal(HazelInternalErrors.ReliablePacketWithoutResponse, $"Reliable packet {self.Id} (size={this.Length}) was not ack'd after {self.Retransmissions} resends ({lifetimeMs}ms)"); - - self.Recycle(); - } +#if DEBUG + this.logger.WriteVerbose($"Resent message id {this.Data[1] >> 8 | this.Data[2]} after {lifetimeMs}ms {this.NextTimeoutMs - lifetimeMs}ms delta (Forced: {force})"); +#endif - return 0; - } + if (force) + { + this.NextTimeoutMs = lifetimeMs; + } - this.NextTimeoutMs += (int)Math.Min(this.NextTimeoutMs * connection.ResendPingMultiplier, MaxAdditionalResendDelayMs); - try - { - connection.WriteBytesToConnection(this.Data, this.Length); - connection.Statistics.LogMessageResent(); - return 1; - } - catch (InvalidOperationException) - { - connection.DisconnectInternal(HazelInternalErrors.ConnectionDisconnected, "Could not resend data as connection is no longer connected"); - } + this.NextTimeoutMs = connection.CalculateNextResendDelayMs(this.NextTimeoutMs); + try + { + connection.WriteBytesToConnection(this.Data, this.Length); + connection.Statistics.LogMessageResent(); + return 1; + } + catch (InvalidOperationException) + { + connection.DisconnectInternal(HazelInternalErrors.ConnectionDisconnected, "Could not resend data as connection is no longer connected"); } } @@ -221,12 +252,13 @@ protected void AttachReliableID(SmartBuffer buffer, int offset, int length, Acti int resendDelayMs = this.ResendTimeoutMs; if (resendDelayMs <= 0) { - resendDelayMs = (_pingMs * this.ResendPingMultiplier).ClampToInt(MinResendDelayMs, MaxInitialResendDelayMs); + resendDelayMs = (_pingMs * this.ResendPingMultiplier).ClampToInt(this.MinResendDelayMs, this.MaxInitialResendDelayMs); } Packet packet = this.PacketPool.GetObject(); packet.Set( id, + this.logger, buffer, length, resendDelayMs, @@ -238,11 +270,9 @@ protected void AttachReliableID(SmartBuffer buffer, int offset, int length, Acti } } - public static int ClampToInt(float value, int min, int max) + public int CalculateNextResendDelayMs(int lastDelayMs) { - if (value < min) return min; - if (value > max) return max; - return (int)value; + return lastDelayMs + (int)Math.Min(lastDelayMs * this.ResendPingMultiplier, this.MaxAdditionalResendDelayMs); } /// @@ -274,8 +304,7 @@ private void ReliableSend(byte sendOption, byte[] data, Action ackCallback = nul /// The buffer received. private void ReliableMessageReceive(MessageReader message, int bytesReceived) { - ushort id; - if (ProcessReliableReceive(message.Buffer, 1, out id)) + if (ProcessReliableReceive(message.Buffer, 1)) { InvokeDataReceived(SendOption.Reliable, message, 3, bytesReceived); } @@ -293,13 +322,13 @@ private void ReliableMessageReceive(MessageReader message, int bytesReceived) /// The buffer containing the data. /// The offset of the reliable header. /// Whether the packet was a new packet or not. - private bool ProcessReliableReceive(byte[] bytes, int offset, out ushort id) + private bool ProcessReliableReceive(byte[] bytes, int offset) { byte b1 = bytes[offset]; byte b2 = bytes[offset + 1]; //Get the ID form the packet - id = (ushort)((b1 << 8) + b2); + ushort id = (ushort)((b1 << 8) + b2); /* * It gets a little complicated here (note the fact I'm actually using a multiline comment for once...) @@ -401,6 +430,10 @@ private void AcknowledgementMessageReceive(byte[] bytes, int bytesReceived) { AcknowledgeMessageId((ushort)(id - i)); } + else + { + ForceResendMessageId((ushort)(id - i)); + } recentPackets >>= 1; } @@ -409,6 +442,14 @@ private void AcknowledgementMessageReceive(byte[] bytes, int bytesReceived) Statistics.LogAcknowledgementReceive(bytesReceived); } + private void ForceResendMessageId(ushort id) + { + if (this.reliableDataPacketsSent.TryGetValue(id, out Packet pkt)) + { + pkt.Resend(force: true); + } + } + private void AcknowledgeMessageId(ushort id) { // Dispose of timer and remove from dictionary @@ -424,6 +465,10 @@ private void AcknowledgeMessageId(ushort id) { this._pingMs = this._pingMs * .7f + rt * .3f; } + +#if DEBUG + this.logger.WriteVerbose($"Packet {id} RTT: {rt}ms Ping:{this._pingMs} Active: {reliableDataPacketsSent.Count}/{activePingPackets.Count}"); +#endif } else if (this.activePingPackets.TryRemove(id, out PingPacket pingPkt)) { @@ -436,6 +481,10 @@ private void AcknowledgeMessageId(ushort id) { this._pingMs = this._pingMs * .7f + rt * .3f; } + +#if DEBUG + this.logger.WriteVerbose($"Ping {id} RTT: {rt}ms Ping:{this._pingMs} Active: {reliableDataPacketsSent.Count}/{activePingPackets.Count}"); +#endif } } diff --git a/Hazel/Udp/UdpConnection.cs b/Hazel/Udp/UdpConnection.cs index 1874597..1fd4d29 100644 --- a/Hazel/Udp/UdpConnection.cs +++ b/Hazel/Udp/UdpConnection.cs @@ -131,7 +131,6 @@ protected virtual void HandleSend(byte[] data, byte sendOption, Action ackCallba /// The buffer containing the bytes received. protected internal virtual void HandleReceive(MessageReader message, int bytesReceived) { - ushort id; switch (message.Buffer[0]) { //Handle reliable receives @@ -147,12 +146,13 @@ protected internal virtual void HandleReceive(MessageReader message, int bytesRe //We need to acknowledge hello and ping messages but dont want to invoke any events! case (byte)UdpSendOption.Ping: - ProcessReliableReceive(message.Buffer, 1, out id); - Statistics.LogHelloReceive(bytesReceived); + ProcessReliableReceive(message.Buffer, 1); + Statistics.LogPingReceive(bytesReceived); message.Recycle(); break; + case (byte)UdpSendOption.Hello: - ProcessReliableReceive(message.Buffer, 1, out id); + ProcessReliableReceive(message.Buffer, 1); Statistics.LogHelloReceive(bytesReceived); message.Recycle(); break; @@ -243,6 +243,7 @@ protected void SendHello(byte[] bytes, Action acknowledgeCallback) } HandleSend(actualBytes, (byte)UdpSendOption.Hello, acknowledgeCallback); + Statistics.LogHelloSend(); } /// diff --git a/Hazel/Udp/UnityUdpClientConnection.cs b/Hazel/Udp/UnityUdpClientConnection.cs index 05eab93..ce6eac5 100644 --- a/Hazel/Udp/UnityUdpClientConnection.cs +++ b/Hazel/Udp/UnityUdpClientConnection.cs @@ -41,7 +41,7 @@ public UnityUdpClientConnection(ILogger logger, IPEndPoint remoteEndPoint, IPMod this.Dispose(false); } - public void FixedUpdate() + public int FixedUpdate() { try { @@ -54,12 +54,14 @@ public void FixedUpdate() try { - ManageReliablePackets(); + return ManageReliablePackets(); } catch (Exception e) { this.logger.WriteError("FixedUpdate: " + e); } + + return 0; } protected virtual void RestartConnection()