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()