diff --git a/Demos/Unity/Client/Assets/Scripts/Multiplayer/RiptideNetworking.dll b/Demos/Unity/Client/Assets/Scripts/Multiplayer/RiptideNetworking.dll
index 0da2812a..aecc56d1 100644
Binary files a/Demos/Unity/Client/Assets/Scripts/Multiplayer/RiptideNetworking.dll and b/Demos/Unity/Client/Assets/Scripts/Multiplayer/RiptideNetworking.dll differ
diff --git a/Demos/Unity/Client/Assets/Scripts/Multiplayer/RiptideNetworking.xml b/Demos/Unity/Client/Assets/Scripts/Multiplayer/RiptideNetworking.xml
index 2337297d..3cf1b1bc 100644
--- a/Demos/Unity/Client/Assets/Scripts/Multiplayer/RiptideNetworking.xml
+++ b/Demos/Unity/Client/Assets/Scripts/Multiplayer/RiptideNetworking.xml
@@ -256,7 +256,7 @@
The position in the byte array that the next bytes will be read from.
- Initializes a reusable Message instance.
+ Initializes a reusable instance.
The maximum amount of bytes the message can contain.
@@ -303,7 +303,7 @@
Adds a single to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves a from the message.
@@ -320,7 +320,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves a array from the message.
@@ -353,7 +353,7 @@
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves a from the message.
@@ -370,7 +370,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves a array from the message.
@@ -403,12 +403,12 @@
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves a from the message.
@@ -432,7 +432,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Adds a array to the message.
@@ -445,7 +445,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves a array from the message.
@@ -506,12 +506,12 @@
Adds an to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves an from the message.
@@ -532,7 +532,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Adds a array to the message.
@@ -545,7 +545,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves an array from the message.
@@ -606,12 +606,12 @@
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves a from the message.
@@ -632,7 +632,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Adds a array to the message.
@@ -645,7 +645,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves a array from the message.
@@ -706,7 +706,7 @@
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves a from the message.
@@ -723,7 +723,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves a array from the message.
@@ -756,7 +756,7 @@
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves a from the message.
@@ -773,7 +773,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves a array from the message.
@@ -806,7 +806,7 @@
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves a from the message.
@@ -823,7 +823,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves a array from the message.
diff --git a/Demos/Unity/Server/Assets/Scripts/Multiplayer/RiptideNetworking.dll b/Demos/Unity/Server/Assets/Scripts/Multiplayer/RiptideNetworking.dll
index 0da2812a..aecc56d1 100644
Binary files a/Demos/Unity/Server/Assets/Scripts/Multiplayer/RiptideNetworking.dll and b/Demos/Unity/Server/Assets/Scripts/Multiplayer/RiptideNetworking.dll differ
diff --git a/Demos/Unity/Server/Assets/Scripts/Multiplayer/RiptideNetworking.xml b/Demos/Unity/Server/Assets/Scripts/Multiplayer/RiptideNetworking.xml
index 2337297d..3cf1b1bc 100644
--- a/Demos/Unity/Server/Assets/Scripts/Multiplayer/RiptideNetworking.xml
+++ b/Demos/Unity/Server/Assets/Scripts/Multiplayer/RiptideNetworking.xml
@@ -256,7 +256,7 @@
The position in the byte array that the next bytes will be read from.
- Initializes a reusable Message instance.
+ Initializes a reusable instance.
The maximum amount of bytes the message can contain.
@@ -303,7 +303,7 @@
Adds a single to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves a from the message.
@@ -320,7 +320,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves a array from the message.
@@ -353,7 +353,7 @@
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves a from the message.
@@ -370,7 +370,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves a array from the message.
@@ -403,12 +403,12 @@
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves a from the message.
@@ -432,7 +432,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Adds a array to the message.
@@ -445,7 +445,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves a array from the message.
@@ -506,12 +506,12 @@
Adds an to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves an from the message.
@@ -532,7 +532,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Adds a array to the message.
@@ -545,7 +545,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves an array from the message.
@@ -606,12 +606,12 @@
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves a from the message.
@@ -632,7 +632,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Adds a array to the message.
@@ -645,7 +645,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves a array from the message.
@@ -706,7 +706,7 @@
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves a from the message.
@@ -723,7 +723,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves a array from the message.
@@ -756,7 +756,7 @@
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves a from the message.
@@ -773,7 +773,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves a array from the message.
@@ -806,7 +806,7 @@
Adds a to the message.
The to add.
- The Message instance that the was added to.
+ The message that the was added to.
Retrieves a from the message.
@@ -823,7 +823,7 @@
Writes the length using 1 if .
- The Message instance that the array was added to.
+ The message that the array was added to.
Retrieves a array from the message.
diff --git a/UnityPackage/README.md b/UnityPackage/README.md
index 66e058a2..6b94ba4a 100644
--- a/UnityPackage/README.md
+++ b/UnityPackage/README.md
@@ -2,8 +2,8 @@
RiptideNetworking is a lightweight C# networking library primarily designed for use in multiplayer games. It can be used in Unity as well as in other .NET environments such as console applications.
-Riptide provides functionality for establishing connections and sending data back and forth. Being a rather low-level solution, it leaves it up to you to decide what data you want to send and when, which is ideal if you like to be in control of your code and know what's going on under the hood.
+Riptide provides functionality for establishing connections and sending data back and forth, leaving it up to you to decide what data you want to send and when, which is ideal if you like to be in control of your code and know what's going on under the hood.
-Riptide is 100% free to use and only funded by [donations](https://ko-fi.com/Y8Y21O02J).
+Riptide is 100% free to use and only funded by [donations](https://github.com/sponsors/tom-weiland).
-For additional information and setup instructions, see the [repo on GitHub](https://github.com/tom-weiland/RiptideNetworking/blob/main/README.md).
\ No newline at end of file
+For additional information and setup instructions, see the [repo on GitHub](https://github.com/tom-weiland/RiptideNetworking).
\ No newline at end of file
diff --git a/UnityPackage/Runtime/Client.cs b/UnityPackage/Runtime/Client.cs
index 2759816e..a9e01cfc 100644
--- a/UnityPackage/Runtime/Client.cs
+++ b/UnityPackage/Runtime/Client.cs
@@ -4,6 +4,7 @@
// For additional information please see the included LICENSE.md file or view it on GitHub: https://github.com/tom-weiland/RiptideNetworking/blob/main/LICENSE.md
using RiptideNetworking.Transports;
+using RiptideNetworking.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -39,12 +40,6 @@ public class Client : Common
public bool IsConnecting => client.IsConnecting;
///
public bool IsConnected => client.IsConnected;
- ///
- public override bool ShouldOutputInfoLogs
- {
- get => client.ShouldOutputInfoLogs;
- set => client.ShouldOutputInfoLogs = value;
- }
/// Encapsulates a method that handles a message from the server.
/// The message that was received.
public delegate void MessageHandler(Message message);
@@ -65,6 +60,15 @@ public override bool ShouldOutputInfoLogs
/// The name to use when logging messages via .
public Client(ushort timeoutTime = 5000, ushort heartbeatInterval = 1000, byte maxConnectionAttempts = 5, string logName = "CLIENT") => client = new Transports.RudpTransport.RudpClient(timeoutTime, heartbeatInterval, maxConnectionAttempts, logName);
+ /// Disconnects the client if it's connected and swaps out the transport it's using.
+ /// The underlying client that is used for managing the connection to the server.
+ /// This method does not automatically reconnect to the server. To continue communicating with the server, will need to be called again.
+ public void ChangeTransport(IClient client)
+ {
+ Disconnect();
+ this.client = client;
+ }
+
/// Attempts connect to the given host address.
/// The host address to connect to.
/// The ID of the group of message handler methods to use when building .
@@ -74,8 +78,7 @@ public override bool ShouldOutputInfoLogs
///
public void Connect(string hostAddress, byte messageHandlerGroupId = 0)
{
- if (IsConnecting || IsConnected)
- Disconnect();
+ Disconnect();
CreateMessageHandlersDictionary(Assembly.GetCallingAssembly(), messageHandlerGroupId);
@@ -105,7 +108,7 @@ protected override void CreateMessageHandlersDictionary(Assembly assembly, byte
if (!methods[i].IsStatic)
{
- RiptideLogger.Log("ERROR", $"Message handler methods should be static, but '{methods[i].DeclaringType}.{methods[i].Name}' is an instance method!");
+ RiptideLogger.Log(LogType.error, $"Message handler methods should be static, but '{methods[i].DeclaringType}.{methods[i].Name}' is an instance method!");
break;
}
@@ -114,7 +117,7 @@ protected override void CreateMessageHandlersDictionary(Assembly assembly, byte
{
// It's a message handler for Client instances
if (messageHandlers.ContainsKey(attribute.MessageId))
- RiptideLogger.Log("ERROR", $"Message handler method (type: client) already exists for message ID {attribute.MessageId}! Only one handler method is allowed per ID!");
+ RiptideLogger.Log(LogType.error, $"Message handler method (type: client) already exists for message ID {attribute.MessageId}! Only one handler method is allowed per ID!");
else
messageHandlers.Add(attribute.MessageId, (MessageHandler)clientMessageHandler);
}
@@ -123,7 +126,7 @@ protected override void CreateMessageHandlersDictionary(Assembly assembly, byte
// It's not a message handler for Client instances, but it might be one for Server instances
Delegate serverMessageHandler = Delegate.CreateDelegate(typeof(Server.MessageHandler), methods[i], false);
if (serverMessageHandler == null)
- RiptideLogger.Log("ERROR", $"'{methods[i].DeclaringType}.{methods[i].Name}' doesn't match any acceptable message handler method signatures, double-check its parameters!");
+ RiptideLogger.Log(LogType.error, $"'{methods[i].DeclaringType}.{methods[i].Name}' doesn't match any acceptable message handler method signatures, double-check its parameters!");
}
}
}
@@ -169,7 +172,7 @@ private void OnMessageReceived(object s, ClientMessageReceivedEventArgs e)
if (messageHandlers.TryGetValue(e.MessageId, out MessageHandler messageHandler))
messageHandler(e.Message);
else
- RiptideLogger.Log("ERROR", $"No handler method (type: client) found for message ID {e.MessageId}!");
+ RiptideLogger.Log(LogType.warning, $"No handler method (type: client) found for message ID {e.MessageId}!");
}
/// Invokes the event.
diff --git a/UnityPackage/Runtime/Common.cs b/UnityPackage/Runtime/Common.cs
index 6cb7c263..62287b54 100644
--- a/UnityPackage/Runtime/Common.cs
+++ b/UnityPackage/Runtime/Common.cs
@@ -11,9 +11,6 @@ namespace RiptideNetworking
/// Contains shared functionality for and .
public abstract class Common
{
- ///
- public abstract bool ShouldOutputInfoLogs { get; set; }
-
/// Searches the given assembly for methods with the and adds them to the dictionary of handler methods.
/// The assembly to search for methods with the .
/// The ID of the group of message handler methods to use when building the message handlers dictionary.
diff --git a/UnityPackage/Runtime/Message.cs b/UnityPackage/Runtime/Message.cs
index c0cd973a..70a6cd6c 100644
--- a/UnityPackage/Runtime/Message.cs
+++ b/UnityPackage/Runtime/Message.cs
@@ -3,7 +3,7 @@
// Copyright (c) 2021 Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub: https://github.com/tom-weiland/RiptideNetworking/blob/main/LICENSE.md
-using RiptideNetworking.Transports.Utils;
+using RiptideNetworking.Utils;
using System;
using System.Collections.Generic;
using System.Text;
@@ -47,6 +47,9 @@ public enum HeaderType : byte
/// Provides functionality for converting data to bytes and vice versa.
public class Message
{
+ /// The maximum amount of bytes that a message can contain. Includes a 1 byte header.
+ public const int MaxMessageSize = 1250;
+
/// How many messages to add to the pool for each or instance that is started.
/// Changes will not affect and instances which are already running until they are restarted.
public static byte InstancesPerSocket { get; set; } = 4;
@@ -74,9 +77,9 @@ public class Message
/// The position in the byte array that the next bytes will be read from.
private ushort readPos = 0;
- /// Initializes a reusable Message instance.
+ /// Initializes a reusable instance.
/// The maximum amount of bytes the message can contain.
- internal Message(ushort maxSize = 1280)
+ internal Message(int maxSize = MaxMessageSize)
{
Bytes = new byte[maxSize];
}
@@ -151,9 +154,7 @@ private static Message RetrieveFromPool()
return message;
}
}
- #endregion
-
- #region Functions
+
/// Returns the message instance to the internal pool so it can be reused.
public void Release()
{
@@ -167,7 +168,9 @@ public void Release()
}
}
}
+ #endregion
+ #region Functions
/// Prepares a message to be used for sending.
/// The header of the message.
/// How often to try sending the message before giving up.
@@ -201,7 +204,7 @@ public HeaderType PrepareForUse(ushort contentLength)
#region Byte
/// Adds a single to the message.
/// The to add.
- /// The Message instance that the was added to.
+ /// The message that the was added to.
public Message Add(byte value)
{
if (UnwrittenLength < 1)
@@ -217,7 +220,7 @@ public byte GetByte()
{
if (UnreadLength < 1)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'byte', returning 0!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'byte', returning 0!");
return 0;
}
@@ -234,7 +237,7 @@ public byte GetByte()
/// Writes the length using 1 if .
///
///
- /// The Message instance that the array was added to.
+ /// The message that the array was added to.
public Message Add(byte[] array, bool includeLength = true, bool isBigArray = false)
{
if (includeLength)
@@ -302,7 +305,7 @@ private void ReadBytes(int amount, byte[] array, int startIndex = 0)
{
if (UnreadLength < amount)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'byte[]', array will contain default elements!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'byte[]', array will contain default elements!");
amount = UnreadLength;
}
@@ -314,7 +317,7 @@ private void ReadBytes(int amount, byte[] array, int startIndex = 0)
#region Bool
/// Adds a to the message.
/// The to add.
- /// The Message instance that the was added to.
+ /// The message that the was added to.
public Message Add(bool value)
{
if (UnwrittenLength < RiptideConverter.boolLength)
@@ -330,7 +333,7 @@ public bool GetBool()
{
if (UnreadLength < RiptideConverter.boolLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'bool', returning false!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'bool', returning false!");
return false;
}
@@ -347,7 +350,7 @@ public bool GetBool()
/// Writes the length using 1 if .
///
///
- /// The Message instance that the array was added to.
+ /// The message that the array was added to.
public Message Add(bool[] array, bool includeLength = true, bool isBigArray = false)
{
if (includeLength)
@@ -411,7 +414,7 @@ public bool[] GetBools(int amount)
int byteAmount = amount / 8 + (amount % 8 == 0 ? 0 : 1);
if (UnreadLength < byteAmount)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'bool[]', array will contain default elements!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'bool[]', array will contain default elements!");
byteAmount = UnreadLength;
}
@@ -429,7 +432,7 @@ public void GetBools(int amount, bool[] array, int startIndex = 0)
int byteAmount = amount / 8 + (amount % 8 == 0 ? 0 : 1);
if (UnreadLength < byteAmount)
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'bool[]', array will contain default elements!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'bool[]', array will contain default elements!");
ReadBools(byteAmount, array, startIndex);
}
@@ -459,7 +462,7 @@ private void ReadBools(int byteAmount, bool[] array, int startIndex = 0)
#region Short & UShort
/// Adds a to the message.
/// The to add.
- /// The Message instance that the was added to.
+ /// The message that the was added to.
public Message Add(short value)
{
if (UnwrittenLength < RiptideConverter.shortLength)
@@ -472,7 +475,7 @@ public Message Add(short value)
/// Adds a to the message.
/// The to add.
- /// The Message instance that the was added to.
+ /// The message that the was added to.
public Message Add(ushort value)
{
if (UnwrittenLength < RiptideConverter.ushortLength)
@@ -489,7 +492,7 @@ public short GetShort()
{
if (UnreadLength < RiptideConverter.shortLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'short', returning 0!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'short', returning 0!");
return 0;
}
@@ -504,7 +507,7 @@ public ushort GetUShort()
{
if (UnreadLength < RiptideConverter.ushortLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'ushort', returning 0!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'ushort', returning 0!");
return 0;
}
@@ -518,7 +521,7 @@ internal ushort PeekUShort()
{
if (UnreadLength < RiptideConverter.ushortLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to peek type 'ushort', returning 0!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to peek type 'ushort', returning 0!");
return 0;
}
@@ -535,7 +538,7 @@ internal ushort PeekUShort()
/// Writes the length using 1 if .
///
///
- /// The Message instance that the array was added to.
+ /// The message that the array was added to.
public Message Add(short[] array, bool includeLength = true, bool isBigArray = false)
{
if (includeLength)
@@ -569,7 +572,7 @@ public Message Add(short[] array, bool includeLength = true, bool isBigArray = f
/// Writes the length using 1 if .
///
///
- /// The Message instance that the array was added to.
+ /// The message that the array was added to.
public Message Add(ushort[] array, bool includeLength = true, bool isBigArray = false)
{
if (includeLength)
@@ -675,7 +678,7 @@ private void ReadShorts(int amount, short[] array, int startIndex = 0)
{
if (UnreadLength < amount * RiptideConverter.shortLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'short[]', array will contain default elements!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'short[]', array will contain default elements!");
amount = UnreadLength / RiptideConverter.shortLength;
}
@@ -694,7 +697,7 @@ private void ReadUShorts(int amount, ushort[] array, int startIndex = 0)
{
if (UnreadLength < amount * RiptideConverter.ushortLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'ushort[]', array will contain default elements!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'ushort[]', array will contain default elements!");
amount = UnreadLength / RiptideConverter.shortLength;
}
@@ -709,7 +712,7 @@ private void ReadUShorts(int amount, ushort[] array, int startIndex = 0)
#region Int & UInt
/// Adds an to the message.
/// The to add.
- /// The Message instance that the was added to.
+ /// The message that the was added to.
public Message Add(int value)
{
if (UnwrittenLength < RiptideConverter.intLength)
@@ -722,7 +725,7 @@ public Message Add(int value)
/// Adds a to the message.
/// The to add.
- /// The Message instance that the was added to.
+ /// The message that the was added to.
public Message Add(uint value)
{
if (UnwrittenLength < RiptideConverter.uintLength)
@@ -739,7 +742,7 @@ public int GetInt()
{
if (UnreadLength < RiptideConverter.intLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'int', returning 0!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'int', returning 0!");
return 0;
}
@@ -754,7 +757,7 @@ public uint GetUInt()
{
if (UnreadLength < RiptideConverter.uintLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'uint', returning 0!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'uint', returning 0!");
return 0;
}
@@ -773,7 +776,7 @@ public uint GetUInt()
/// Writes the length using 1 if .
///
///
- /// The Message instance that the array was added to.
+ /// The message that the array was added to.
public Message Add(int[] array, bool includeLength = true, bool isBigArray = false)
{
if (includeLength)
@@ -807,7 +810,7 @@ public Message Add(int[] array, bool includeLength = true, bool isBigArray = fal
/// Writes the length using 1 if .
///
///
- /// The Message instance that the array was added to.
+ /// The message that the array was added to.
public Message Add(uint[] array, bool includeLength = true, bool isBigArray = false)
{
if (includeLength)
@@ -913,7 +916,7 @@ private void ReadInts(int amount, int[] array, int startIndex = 0)
{
if (UnreadLength < amount * RiptideConverter.intLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'int[]', array will contain default elements!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'int[]', array will contain default elements!");
amount = UnreadLength / RiptideConverter.intLength;
}
@@ -932,7 +935,7 @@ private void ReadUInts(int amount, uint[] array, int startIndex = 0)
{
if (UnreadLength < amount * RiptideConverter.uintLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'uint[]', array will contain default elements!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'uint[]', array will contain default elements!");
amount = UnreadLength / RiptideConverter.uintLength;
}
@@ -947,7 +950,7 @@ private void ReadUInts(int amount, uint[] array, int startIndex = 0)
#region Long & ULong
/// Adds a to the message.
/// The to add.
- /// The Message instance that the was added to.
+ /// The message that the was added to.
public Message Add(long value)
{
if (UnwrittenLength < RiptideConverter.longLength)
@@ -960,7 +963,7 @@ public Message Add(long value)
/// Adds a to the message.
/// The to add.
- /// The Message instance that the was added to.
+ /// The message that the was added to.
public Message Add(ulong value)
{
if (UnwrittenLength < RiptideConverter.ulongLength)
@@ -977,7 +980,7 @@ public long GetLong()
{
if (UnreadLength < RiptideConverter.longLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'long', returning 0!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'long', returning 0!");
return 0;
}
@@ -992,7 +995,7 @@ public ulong GetULong()
{
if (UnreadLength < RiptideConverter.ulongLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'ulong', returning 0!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'ulong', returning 0!");
return 0;
}
@@ -1011,7 +1014,7 @@ public ulong GetULong()
/// Writes the length using 1 if .
///
///
- /// The Message instance that the array was added to.
+ /// The message that the array was added to.
public Message Add(long[] array, bool includeLength = true, bool isBigArray = false)
{
if (includeLength)
@@ -1045,7 +1048,7 @@ public Message Add(long[] array, bool includeLength = true, bool isBigArray = fa
/// Writes the length using 1 if .
///
///
- /// The Message instance that the array was added to.
+ /// The message that the array was added to.
public Message Add(ulong[] array, bool includeLength = true, bool isBigArray = false)
{
if (includeLength)
@@ -1151,7 +1154,7 @@ private void ReadLongs(int amount, long[] array, int startIndex = 0)
{
if (UnreadLength < amount * RiptideConverter.longLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'long[]', array will contain default elements!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'long[]', array will contain default elements!");
amount = UnreadLength / RiptideConverter.longLength;
}
@@ -1170,7 +1173,7 @@ private void ReadULongs(int amount, ulong[] array, int startIndex = 0)
{
if (UnreadLength < amount * RiptideConverter.ulongLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'ulong[]', array will contain default elements!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'ulong[]', array will contain default elements!");
amount = UnreadLength / RiptideConverter.ulongLength;
}
@@ -1185,7 +1188,7 @@ private void ReadULongs(int amount, ulong[] array, int startIndex = 0)
#region Float
/// Adds a to the message.
/// The to add.
- /// The Message instance that the was added to.
+ /// The message that the was added to.
public Message Add(float value)
{
if (UnwrittenLength < RiptideConverter.floatLength)
@@ -1202,7 +1205,7 @@ public float GetFloat()
{
if (UnreadLength < RiptideConverter.floatLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'float', returning 0!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'float', returning 0!");
return 0;
}
@@ -1221,7 +1224,7 @@ public float GetFloat()
/// Writes the length using 1 if .
///
///
- /// The Message instance that the array was added to.
+ /// The message that the array was added to.
public Message Add(float[] array, bool includeLength = true, bool isBigArray = false)
{
if (includeLength)
@@ -1290,7 +1293,7 @@ private void ReadFloats(int amount, float[] array, int startIndex = 0)
{
if (UnreadLength < amount * RiptideConverter.floatLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'float[]', array will contain default elements!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'float[]', array will contain default elements!");
amount = UnreadLength / RiptideConverter.floatLength;
}
@@ -1305,7 +1308,7 @@ private void ReadFloats(int amount, float[] array, int startIndex = 0)
#region Double
/// Adds a to the message.
/// The to add.
- /// The Message instance that the was added to.
+ /// The message that the was added to.
public Message Add(double value)
{
if (UnwrittenLength < RiptideConverter.doubleLength)
@@ -1322,7 +1325,7 @@ public double GetDouble()
{
if (UnreadLength < RiptideConverter.doubleLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'double', returning 0!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'double', returning 0!");
return 0;
}
@@ -1341,7 +1344,7 @@ public double GetDouble()
/// Writes the length using 1 if .
///
///
- /// The Message instance that the array was added to.
+ /// The message that the array was added to.
public Message Add(double[] array, bool includeLength = true, bool isBigArray = false)
{
if (includeLength)
@@ -1410,7 +1413,7 @@ private void ReadDoubles(int amount, double[] array, int startIndex = 0)
{
if (UnreadLength < amount * RiptideConverter.doubleLength)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'double[]', array will contain default elements!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'double[]', array will contain default elements!");
amount = UnreadLength / RiptideConverter.doubleLength;
}
@@ -1425,7 +1428,7 @@ private void ReadDoubles(int amount, double[] array, int startIndex = 0)
#region String
/// Adds a to the message.
/// The to add.
- /// The Message instance that the was added to.
+ /// The message that the was added to.
public Message Add(string value)
{
byte[] stringBytes = Encoding.UTF8.GetBytes(value);
@@ -1445,7 +1448,7 @@ public string GetString()
ushort length = GetUShort(); // Get the length of the string (in bytes, NOT characters)
if (UnreadLength < length)
{
- RiptideLogger.Log("ERROR", $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'string', result will be truncated!");
+ RiptideLogger.Log(LogType.error, $"Message contains insufficient unread bytes ({UnreadLength}) to read type 'string', result will be truncated!");
length = (ushort)UnreadLength;
}
@@ -1464,7 +1467,7 @@ public string GetString()
/// Writes the length using 1 if .
///
///
- /// The Message instance that the array was added to.
+ /// The message that the array was added to.
public Message Add(string[] array, bool includeLength = true, bool isBigArray = false)
{
if (includeLength)
diff --git a/UnityPackage/Runtime/MessageExtensionsUnity.cs b/UnityPackage/Runtime/MessageExtensionsUnity.cs
new file mode 100644
index 00000000..068f4d2f
--- /dev/null
+++ b/UnityPackage/Runtime/MessageExtensionsUnity.cs
@@ -0,0 +1,72 @@
+
+// This file is provided under The MIT License as part of RiptideNetworking.
+// Copyright (c) 2021 Tom Weiland
+// For additional information please see the included LICENSE.md file or view it on GitHub: https://github.com/tom-weiland/RiptideNetworking/blob/main/LICENSE.md
+
+using UnityEngine;
+
+namespace RiptideNetworking
+{
+ public static class MessageExtensionsUnity
+ {
+ #region Vector2
+ /// Adds a to the message.
+ /// The to add.
+ /// The message that the was added to.
+ public static Message Add(this Message message, Vector2 value)
+ {
+ message.Add(value.x);
+ message.Add(value.y);
+ return message;
+ }
+
+ /// Retrieves a from the message.
+ /// The that was retrieved.
+ public static Vector2 GetVector2(this Message message)
+ {
+ return new Vector2(message.GetFloat(), message.GetFloat());
+ }
+ #endregion
+
+ #region Vector3
+ /// Adds a to the message.
+ /// The to add.
+ /// The message that the was added to.
+ public static Message Add(this Message message, Vector3 value)
+ {
+ message.Add(value.x);
+ message.Add(value.y);
+ message.Add(value.z);
+ return message;
+ }
+
+ /// Retrieves a from the message.
+ /// The that was retrieved.
+ public static Vector3 GetVector3(this Message message)
+ {
+ return new Vector3(message.GetFloat(), message.GetFloat(), message.GetFloat());
+ }
+ #endregion
+
+ #region Quaternion
+ /// Adds a to the message.
+ /// The to add.
+ /// The message that the was added to.
+ public static Message Add(this Message message, Quaternion value)
+ {
+ message.Add(value.x);
+ message.Add(value.y);
+ message.Add(value.z);
+ message.Add(value.w);
+ return message;
+ }
+
+ /// Retrieves a from the message.
+ /// The that was retrieved.
+ public static Quaternion GetQuaternion(this Message message)
+ {
+ return new Quaternion(message.GetFloat(), message.GetFloat(), message.GetFloat(), message.GetFloat());
+ }
+ #endregion
+ }
+}
diff --git a/UnityPackage/Runtime/MessageExtensionsUnity.cs.meta b/UnityPackage/Runtime/MessageExtensionsUnity.cs.meta
new file mode 100644
index 00000000..f041c360
--- /dev/null
+++ b/UnityPackage/Runtime/MessageExtensionsUnity.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9c16ac42a6d476248905eb8eff67b4b0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityPackage/Runtime/RiptideLogger.cs b/UnityPackage/Runtime/RiptideLogger.cs
deleted file mode 100644
index a610a1db..00000000
--- a/UnityPackage/Runtime/RiptideLogger.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-
-// This file is provided under The MIT License as part of RiptideNetworking.
-// Copyright (c) 2021 Tom Weiland
-// For additional information please see the included LICENSE.md file or view it on GitHub: https://github.com/tom-weiland/RiptideNetworking/blob/main/LICENSE.md
-
-using System;
-
-namespace RiptideNetworking
-{
- /// Provides functionality for logging messages.
- public class RiptideLogger
- {
- /// Encapsulates a method used to log messages.
- /// The message to log.
- public delegate void LogMethod(string log);
- /// The method to use when logging messages.
- private static LogMethod logMethod;
- /// Whether or not to include timestamps when logging messages.
- private static bool includeTimestamps;
- /// The format to use for timestamps.
- private static string timestampFormat;
-
- /// Handles initial setup.
- /// The method to use when logging messages.
- /// Whether or not to include timestamps when logging messages.
- /// The format to use for timestamps.
- public static void Initialize(LogMethod logMethod, bool includeTimestamps, string timestampFormat = "HH:mm:ss")
- {
- RiptideLogger.logMethod = logMethod;
- RiptideLogger.includeTimestamps = includeTimestamps;
- RiptideLogger.timestampFormat = timestampFormat;
- }
-
- /// Logs a message.
- /// The message to log to the console.
- public static void Log(string message)
- {
- if (includeTimestamps)
- logMethod($"[{GetTimestamp(DateTime.Now)}]: {message}");
- else
- logMethod(message);
- }
- /// Logs a message.
- /// Who is logging this message.
- /// The message to log to the console.
- public static void Log(string logName, string message)
- {
- if (includeTimestamps)
- logMethod($"[{GetTimestamp(DateTime.Now)}] ({logName}): {message}");
- else
- logMethod($"({logName}): {message}");
- }
-
- /// Converts a object to a formatted timestamp string.
- /// The time to format.
- /// The formatted timestamp.
- private static string GetTimestamp(DateTime time)
- {
-#if DETAILED_LOGGING
- return time.ToString("HH:mm:ss:fff");
-#else
- return time.ToString(timestampFormat);
-#endif
- }
- }
-}
diff --git a/UnityPackage/Runtime/Server.cs b/UnityPackage/Runtime/Server.cs
index a5242616..75048ad6 100644
--- a/UnityPackage/Runtime/Server.cs
+++ b/UnityPackage/Runtime/Server.cs
@@ -4,6 +4,7 @@
// For additional information please see the included LICENSE.md file or view it on GitHub: https://github.com/tom-weiland/RiptideNetworking/blob/main/LICENSE.md
using RiptideNetworking.Transports;
+using RiptideNetworking.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -31,12 +32,6 @@ public class Server : Common
public ushort MaxClientCount => server.MaxClientCount;
///
public int ClientCount => server.ClientCount;
- ///
- public override bool ShouldOutputInfoLogs
- {
- get => server.ShouldOutputInfoLogs;
- set => server.ShouldOutputInfoLogs = value;
- }
/// Encapsulates a method that handles a message from a certain client.
/// The numeric ID of the client from whom the message was received.
/// The message that was received.
@@ -57,14 +52,22 @@ public override bool ShouldOutputInfoLogs
/// The name to use when logging messages via .
public Server(ushort clientTimeoutTime = 5000, ushort clientHeartbeatInterval = 1000, string logName = "SERVER") => server = new Transports.RudpTransport.RudpServer(clientTimeoutTime, clientHeartbeatInterval, logName);
+ /// Stops the server if it's running and swaps out the transport it's using.
+ /// The underlying server that is used for managing connections and sending and receiving data.
+ /// This method does not automatically restart the server. To continue accepting connections, will need to be called again.
+ public void ChangeTransport(IServer server)
+ {
+ Stop();
+ this.server = server;
+ }
+
/// Starts the server.
/// The local port on which to start the server.
/// The maximum number of concurrent connections to allow.
/// The ID of the group of message handler methods to use when building .
public void Start(ushort port, ushort maxClientCount, byte messageHandlerGroupId = 0)
{
- if (IsRunning)
- Stop();
+ Stop();
CreateMessageHandlersDictionary(Assembly.GetCallingAssembly(), messageHandlerGroupId);
@@ -93,7 +96,7 @@ protected override void CreateMessageHandlersDictionary(Assembly assembly, byte
if (!methods[i].IsStatic)
{
- RiptideLogger.Log("ERROR", $"Message handler methods should be static, but '{methods[i].DeclaringType}.{methods[i].Name}' is an instance method!");
+ RiptideLogger.Log(LogType.error, $"Message handler methods should be static, but '{methods[i].DeclaringType}.{methods[i].Name}' is an instance method!");
break;
}
@@ -102,7 +105,7 @@ protected override void CreateMessageHandlersDictionary(Assembly assembly, byte
{
// It's a message handler for Server instances
if (messageHandlers.ContainsKey(attribute.MessageId))
- RiptideLogger.Log("ERROR", $"Message handler method (type: server) already exists for message ID {attribute.MessageId}! Only one handler method is allowed per ID!");
+ RiptideLogger.Log(LogType.error, $"Message handler method (type: server) already exists for message ID {attribute.MessageId}! Only one handler method is allowed per ID!");
else
messageHandlers.Add(attribute.MessageId, (MessageHandler)clientMessageHandler);
}
@@ -111,7 +114,7 @@ protected override void CreateMessageHandlersDictionary(Assembly assembly, byte
// It's not a message handler for Server instances, but it might be one for Client instances
Delegate serverMessageHandler = Delegate.CreateDelegate(typeof(Client.MessageHandler), methods[i], false);
if (serverMessageHandler == null)
- RiptideLogger.Log("ERROR", $"'{methods[i].DeclaringType}.{methods[i].Name}' doesn't match any acceptable message handler method signatures, double-check its parameters!");
+ RiptideLogger.Log(LogType.error, $"'{methods[i].DeclaringType}.{methods[i].Name}' doesn't match any acceptable message handler method signatures, double-check its parameters!");
}
}
}
@@ -153,7 +156,7 @@ private void OnMessageReceived(object s, ServerMessageReceivedEventArgs e)
if (messageHandlers.TryGetValue(e.MessageId, out MessageHandler messageHandler))
messageHandler(e.FromClientId, e.Message);
else
- RiptideLogger.Log("ERROR", $"No handler method (type: server) found for message ID {e.MessageId}!");
+ RiptideLogger.Log(LogType.warning, $"No handler method (type: server) found for message ID {e.MessageId}!");
}
}
}
diff --git a/UnityPackage/Runtime/Transports/ICommon.cs b/UnityPackage/Runtime/Transports/ICommon.cs
index 8d3717ff..4d8e15de 100644
--- a/UnityPackage/Runtime/Transports/ICommon.cs
+++ b/UnityPackage/Runtime/Transports/ICommon.cs
@@ -12,9 +12,6 @@ namespace RiptideNetworking.Transports
/// Defines methods, properties, and events which every transport's server and client must implement.
public interface ICommon
{
- /// Whether or not to output informational log messages. Error-related log messages ignore this setting.
- bool ShouldOutputInfoLogs { get; set; }
-
/// Initiates handling of currently queued messages.
/// This should generally be called from within a regularly executed update loop (like FixedUpdate in Unity). Messages will continue to be received in between calls, but won't be handled fully until this method is executed.
void Tick();
diff --git a/UnityPackage/Runtime/Transports/RudpTransport/Lockables.cs b/UnityPackage/Runtime/Transports/RudpTransport/Lockables.cs
index 15336ab0..e7dbde6a 100644
--- a/UnityPackage/Runtime/Transports/RudpTransport/Lockables.cs
+++ b/UnityPackage/Runtime/Transports/RudpTransport/Lockables.cs
@@ -9,17 +9,19 @@ namespace RiptideNetworking.Transports.RudpTransport
internal class SendLockables
{
/// The sequence ID of the latest message that we want to acknowledge.
- internal ushort LastReceivedSeqId;
+ internal ushort LastReceivedSeqId { get; set; }
/// Messages that we have received and want to acknowledge.
- internal ushort AcksBitfield;
+ internal ushort AcksBitfield { get; set; }
+ /// Messages that we have received whose sequence IDs no longer fall into 's range, used to improve duplicate message filtering capabilities.
+ internal ulong DuplicateFilterBitfield { get; set; }
}
/// Contains values that are accessed by multiple threads and are used to determine which messages the other end has received.
internal class ReceiveLockables
{
/// The sequence ID of the latest message that we've received an ack for.
- internal ushort LastAckedSeqId;
+ internal ushort LastAckedSeqId { get; set; }
/// Messages that we sent which have been acknoweledged.
- internal ushort AckedMessagesBitfield;
+ internal ushort AckedMessagesBitfield { get; set; }
}
}
diff --git a/UnityPackage/Runtime/Transports/RudpTransport/PendingMessage.cs b/UnityPackage/Runtime/Transports/RudpTransport/PendingMessage.cs
new file mode 100644
index 00000000..bc4f7e7f
--- /dev/null
+++ b/UnityPackage/Runtime/Transports/RudpTransport/PendingMessage.cs
@@ -0,0 +1,184 @@
+
+// This file is provided under The MIT License as part of RiptideNetworking.
+// Copyright (c) 2021 Tom Weiland
+// For additional information please see the included LICENSE.md file or view it on GitHub: https://github.com/tom-weiland/RiptideNetworking/blob/main/LICENSE.md
+
+using RiptideNetworking.Utils;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Timers;
+
+namespace RiptideNetworking.Transports.RudpTransport
+{
+ /// Represents a currently pending reliably sent message whose delivery has not been acknowledged yet.
+ internal class PendingMessage
+ {
+ /// The multiplier used to determine how long to wait before resending a pending message.
+ private const float retryTimeMultiplier = 1.2f;
+
+ /// A pool of reusable instances.
+ private static readonly List pool = new List();
+
+ /// The to use to send (and resend) the pending message.
+ private RudpPeer peer;
+ /// The intended destination endpoint of the message.
+ private IPEndPoint remoteEndPoint;
+ /// The sequence ID of the message.
+ private ushort sequenceId;
+ /// The contents of the message.
+ private byte[] data;
+ /// The length in bytes of the data that has been written to the message.
+ private int writtenLength;
+ /// How often to try sending the message before giving up.
+ private int maxSendAttempts;
+ /// How many send attempts have been made so far.
+ private byte sendAttempts;
+ /// The time of the latest send attempt.
+ private DateTime lastSendTime;
+ /// The timer responsible for triggering a resend, if all else fails (like acks getting lost or redundant acks not being updated fast enough).
+ private readonly Timer retryTimer;
+ /// Whether the pending message has been cleared or not.
+ private bool wasCleared;
+
+ /// Handles initial setup.
+ internal PendingMessage()
+ {
+ data = new byte[Message.MaxMessageSize + RiptideConverter.ushortLength]; // + ushort length because we need to add the sequence ID bytes
+
+ retryTimer = new Timer();
+ retryTimer.Elapsed += (s, e) => RetrySend();
+ retryTimer.AutoReset = false;
+ }
+
+ #region Pooling
+ /// Retrieves a instance, initializes it and then sends it.
+ /// The to use to send (and resend) the pending message.
+ /// The sequence ID of the message.
+ /// The message that is being sent reliably.
+ /// The intended destination endpoint of the message.
+ internal static void CreateAndSend(RudpPeer peer, ushort sequenceId, Message message, IPEndPoint toEndPoint)
+ {
+ PendingMessage pendingMessage = RetrieveFromPool();
+ pendingMessage.peer = peer;
+ pendingMessage.sequenceId = sequenceId;
+
+ pendingMessage.data[0] = message.Bytes[0]; // Copy message header
+ RiptideConverter.FromUShort(sequenceId, pendingMessage.data, 1); // Insert sequence ID
+ Array.Copy(message.Bytes, 1, pendingMessage.data, 3, message.WrittenLength - 1); // Copy the rest of the message
+ pendingMessage.writtenLength = message.WrittenLength + RiptideConverter.ushortLength;
+
+ pendingMessage.remoteEndPoint = toEndPoint;
+ pendingMessage.maxSendAttempts = message.MaxSendAttempts;
+ pendingMessage.sendAttempts = 0;
+ pendingMessage.wasCleared = false;
+
+ lock (peer.PendingMessages)
+ {
+ peer.PendingMessages.Add(sequenceId, pendingMessage);
+ pendingMessage.TrySend();
+ }
+ }
+
+ /// Retrieves a instance from the pool. If none is available, a new instance is created.
+ /// A instance.
+ private static PendingMessage RetrieveFromPool()
+ {
+ lock (pool)
+ {
+ PendingMessage message;
+ if (pool.Count > 0)
+ {
+ message = pool[0];
+ pool.RemoveAt(0);
+ }
+ else
+ message = new PendingMessage();
+
+ return message;
+ }
+ }
+
+ /// Returns the instance to the internal pool so it can be reused.
+ private void Release()
+ {
+ lock (pool)
+ {
+ if (!pool.Contains(this))
+ pool.Add(this); // Only add it if it's not already in the list, otherwise this method being called twice in a row for whatever reason could cause *serious* issues
+
+ // TODO: consider doing something to decrease pool capacity if there are far more available instance than are needed
+ }
+ }
+ #endregion
+
+ /// Resends the message.
+ internal void RetrySend()
+ {
+ lock (this) // Make sure we don't try resending the message while another thread is clearing it because it was delivered
+ {
+ if (!wasCleared)
+ {
+ if (lastSendTime.AddMilliseconds(peer.SmoothRTT < 0 ? 25 : peer.SmoothRTT * 0.5f) <= DateTime.UtcNow) // Avoid triggering a resend if the latest resend was less than half a RTT ago
+ TrySend();
+ else
+ {
+ retryTimer.Start();
+ retryTimer.Interval = (peer.SmoothRTT < 0 ? 50 : Math.Max(10, peer.SmoothRTT * retryTimeMultiplier));
+ }
+ }
+ }
+ }
+
+ /// Attempts to send the message.
+ internal void TrySend()
+ {
+ if (sendAttempts >= maxSendAttempts)
+ {
+ // Send attempts exceeds max send attempts, so give up
+ if (RiptideLogger.IsWarningLoggingEnabled)
+ {
+ HeaderType headerType = (HeaderType)data[0];
+ if (headerType == HeaderType.reliable)
+ {
+#if BIG_ENDIAN
+ ushort messageId = (ushort)(data[4] | (data[3] << 8));
+#else
+ ushort messageId = (ushort)(data[3] | (data[4] << 8));
+#endif
+ RiptideLogger.Log(LogType.warning, peer.Listener.LogName, $"No ack received for {headerType} message (ID: {messageId}) after {sendAttempts} attempt(s), delivery may have failed!");
+ }
+ else
+ RiptideLogger.Log(LogType.warning, peer.Listener.LogName, $"No ack received for internal {headerType} message after {sendAttempts} attempt(s), delivery may have failed!");
+ }
+
+ Clear();
+ return;
+ }
+
+ peer.Listener.Send(data, writtenLength, remoteEndPoint);
+
+ lastSendTime = DateTime.UtcNow;
+ sendAttempts++;
+
+ retryTimer.Start();
+ retryTimer.Interval = peer.SmoothRTT < 0 ? 50 : Math.Max(10, peer.SmoothRTT * retryTimeMultiplier);
+ }
+
+ /// Clears and removes the message from the dictionary of pending messages.
+ /// Whether or not to remove the message from .
+ internal void Clear(bool shouldRemoveFromDictionary = true)
+ {
+ lock (this)
+ {
+ if (shouldRemoveFromDictionary)
+ lock (peer.PendingMessages)
+ peer.PendingMessages.Remove(sequenceId);
+
+ retryTimer.Stop();
+ wasCleared = true;
+ Release();
+ }
+ }
+ }
+}
diff --git a/UnityPackage/Runtime/Transports/RudpTransport/PendingMessage.cs.meta b/UnityPackage/Runtime/Transports/RudpTransport/PendingMessage.cs.meta
new file mode 100644
index 00000000..73c0769b
--- /dev/null
+++ b/UnityPackage/Runtime/Transports/RudpTransport/PendingMessage.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f456e8298e4041346b7abd5e51103117
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityPackage/Runtime/Transports/RudpTransport/RudpClient.cs b/UnityPackage/Runtime/Transports/RudpTransport/RudpClient.cs
index a136b240..60b830a2 100644
--- a/UnityPackage/Runtime/Transports/RudpTransport/RudpClient.cs
+++ b/UnityPackage/Runtime/Transports/RudpTransport/RudpClient.cs
@@ -3,7 +3,10 @@
// Copyright (c) 2021 Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub: https://github.com/tom-weiland/RiptideNetworking/blob/main/LICENSE.md
+using RiptideNetworking.Utils;
using System;
+using System.Diagnostics;
+using System.Linq;
using System.Net;
using System.Threading;
@@ -71,8 +74,10 @@ public ushort HeartbeatInterval
private DateTime lastHeartbeat;
/// ID of the last ping that was sent.
private byte lastPingId = 0;
- /// The currently pending ping.
- private (byte id, DateTime sendTime) pendingPing;
+ /// The ID of the currently pending ping.
+ private byte pendingPingId;
+ /// The stopwatch that tracks the time since the currently pending ping was sent.
+ private Stopwatch pendingPingStopwatch;
/// Handles initial setup.
/// The time (in milliseconds) after which to disconnect if there's no heartbeat from the server.
@@ -84,6 +89,7 @@ public RudpClient(ushort timeoutTime = 5000, ushort heartbeatInterval = 1000, by
TimeoutTime = timeoutTime;
_heartbeatInterval = heartbeatInterval;
this.maxConnectionAttempts = maxConnectionAttempts;
+ pendingPingStopwatch = new Stopwatch();
}
///
@@ -91,52 +97,68 @@ public RudpClient(ushort timeoutTime = 5000, ushort heartbeatInterval = 1000, by
public void Connect(string hostAddress)
{
string[] ipAndPort = hostAddress.Split(':');
- if (ipAndPort.Length != 2 || !IPAddress.TryParse(ipAndPort[0], out IPAddress ip) || !ushort.TryParse(ipAndPort[1], out ushort port))
+ string ipString = "";
+ string portString = "";
+ if (ipAndPort.Length > 2)
{
- RiptideLogger.Log(LogName, $"Invalid host address '{hostAddress}'! IP and port should be separated by a colon, for example: '127.0.0.1:7777'.");
+ // There was more than one ':' in the host address, might be IPv6
+ ipString = string.Join(":", ipAndPort.Take(ipAndPort.Length - 1));
+ portString = ipAndPort[ipAndPort.Length - 1];
+ }
+ else if (ipAndPort.Length == 2)
+ {
+ // IPv4
+ ipString = ipAndPort[0];
+ portString = ipAndPort[1];
+ }
+
+ if (!IPAddress.TryParse(ipString, out IPAddress ip) || !ushort.TryParse(portString, out ushort port))
+ {
+ RiptideLogger.Log(LogType.error, LogName, $"Invalid host address '{hostAddress}'! IP and port should be separated by a colon, for example: '127.0.0.1:7777'.");
return;
}
connectionAttempts = 0;
- remoteEndPoint = new IPEndPoint(ip, port);
+ remoteEndPoint = new IPEndPoint(ip.MapToIPv6(), port);
peer = new RudpPeer(this);
StartListening();
connectionState = ConnectionState.connecting;
-
- if (ShouldOutputInfoLogs)
- RiptideLogger.Log(LogName, $"Connecting to {remoteEndPoint}...");
-
- heartbeatTimer = new Timer(Heartbeat, null, 0, HeartbeatInterval);
+
+ heartbeatTimer = new Timer((o) => Heartbeat(), null, 0, HeartbeatInterval);
+ RiptideLogger.Log(LogType.info, LogName, $"Connecting to {remoteEndPoint.ToStringBasedOnIPFormat()}...");
}
/// Sends a connnect or heartbeat message. Called by .
- private void Heartbeat(object state)
+ private void Heartbeat()
{
- if (IsConnecting)
+ receiveActionQueue.Add(() =>
{
- // If still trying to connect, send connect messages instead of heartbeats
- if (connectionAttempts < maxConnectionAttempts)
- {
- SendConnect();
- connectionAttempts++;
- }
- else
+ if (IsConnecting)
{
- OnConnectionFailed();
+ // If still trying to connect, send connect messages instead of heartbeats
+ if (connectionAttempts < maxConnectionAttempts)
+ {
+ SendConnect();
+ connectionAttempts++;
+ }
+ else
+ {
+ OnConnectionFailed();
+ }
}
- }
- else if (IsConnected)
- {
- // If connected and not timed out, send heartbeats
- if (HasTimedOut)
+ else if (IsConnected)
{
- HandleDisconnect();
- return;
- }
+ // If connected and not timed out, send heartbeats
+ if (HasTimedOut)
+ {
+ HandleDisconnect();
+ return;
+ }
- SendHeartbeat();
- }
+ SendHeartbeat();
+ }
+ });
}
///
@@ -195,7 +217,7 @@ protected override void Handle(Message message, IPEndPoint fromEndPoint, HeaderT
HandleDisconnect();
break;
default:
- RiptideLogger.Log(LogName, $"Unknown message header type '{messageHeader}'! Discarding {message.WrittenLength} bytes.");
+ RiptideLogger.Log(LogType.warning, LogName, $"Unknown message header type '{messageHeader}'! Discarding {message.WrittenLength} bytes.");
break;
}
@@ -203,9 +225,9 @@ protected override void Handle(Message message, IPEndPoint fromEndPoint, HeaderT
}
///
- protected override void ReliableHandle(Message message, IPEndPoint fromEndPoint, HeaderType messageHeader)
+ protected override void ReliableHandle(HeaderType messageHeader, ushort sequenceId, Message message, IPEndPoint fromEndPoint)
{
- ReliableHandle(message, fromEndPoint, messageHeader, peer.SendLockables);
+ ReliableHandle(messageHeader, sequenceId, message, fromEndPoint, peer.SendLockables);
}
///
@@ -229,8 +251,7 @@ public void Disconnect()
SendDisconnect();
LocalDisconnect();
- if (ShouldOutputInfoLogs)
- RiptideLogger.Log(LogName, "Disconnected from server (initiated locally).");
+ RiptideLogger.Log(LogType.info, LogName, "Disconnected from server (initiated locally).");
}
/// Cleans up local objects on disconnection.
@@ -243,7 +264,7 @@ private void LocalDisconnect()
lock (peer.PendingMessages)
{
- foreach (RudpPeer.PendingMessage pendingMessage in peer.PendingMessages.Values)
+ foreach (PendingMessage pendingMessage in peer.PendingMessages.Values)
pendingMessage.Clear(false);
peer.PendingMessages.Clear();
@@ -299,10 +320,11 @@ private void HandleAckExtra(Message message)
/// Sends a heartbeat message.
private void SendHeartbeat()
{
- pendingPing = (lastPingId++, DateTime.UtcNow);
+ pendingPingId = lastPingId++;
+ pendingPingStopwatch.Restart();
Message message = Message.Create(HeaderType.heartbeat);
- message.Add(pendingPing.id);
+ message.Add(pendingPingId);
message.Add(peer.RTT);
Send(message);
@@ -314,8 +336,8 @@ private void HandleHeartbeat(Message message)
{
byte pingId = message.GetByte();
- if (pendingPing.id == pingId)
- peer.RTT = (short)Math.Max(1f, (DateTime.UtcNow - pendingPing.sendTime).TotalMilliseconds);
+ if (pendingPingId == pingId)
+ peer.RTT = (short)Math.Max(1f, pendingPingStopwatch.ElapsedMilliseconds);
lastHeartbeat = DateTime.UtcNow;
}
@@ -375,8 +397,7 @@ private void HandleDisconnect()
/// Invokes the event.
private void OnConnected()
{
- if (ShouldOutputInfoLogs)
- RiptideLogger.Log(LogName, "Connected successfully!");
+ RiptideLogger.Log(LogType.info, LogName, "Connected successfully!");
receiveActionQueue.Add(() => Connected?.Invoke(this, EventArgs.Empty));
}
@@ -384,8 +405,7 @@ private void OnConnected()
/// Invokes the event.
private void OnConnectionFailed()
{
- if (ShouldOutputInfoLogs)
- RiptideLogger.Log(LogName, "Connection to server failed!");
+ RiptideLogger.Log(LogType.info, LogName, "Connection to server failed!");
LocalDisconnect();
receiveActionQueue.Add(() => ConnectionFailed?.Invoke(this, EventArgs.Empty));
@@ -401,8 +421,7 @@ private void OnMessageReceived(ClientMessageReceivedEventArgs e)
/// Invokes the event.
private void OnDisconnected()
{
- if (ShouldOutputInfoLogs)
- RiptideLogger.Log(LogName, "Disconnected from server (initiated remotely).");
+ RiptideLogger.Log(LogType.info, LogName, "Disconnected from server (initiated remotely).");
LocalDisconnect();
receiveActionQueue.Add(() => Disconnected?.Invoke(this, EventArgs.Empty));
@@ -412,8 +431,7 @@ private void OnDisconnected()
/// The event args to invoke the event with.
private void OnClientConnected(ClientConnectedEventArgs e)
{
- if (ShouldOutputInfoLogs)
- RiptideLogger.Log(LogName, $"Client {e.Id} connected.");
+ RiptideLogger.Log(LogType.info, LogName, $"Client {e.Id} connected.");
receiveActionQueue.Add(() => ClientConnected?.Invoke(this, e));
}
@@ -422,8 +440,7 @@ private void OnClientConnected(ClientConnectedEventArgs e)
/// The event args to invoke the event with.
private void OnClientDisconnected(ClientDisconnectedEventArgs e)
{
- if (ShouldOutputInfoLogs)
- RiptideLogger.Log(LogName, $"Client {e.Id} disconnected.");
+ RiptideLogger.Log(LogType.info, LogName, $"Client {e.Id} disconnected.");
receiveActionQueue.Add(() => ClientDisconnected?.Invoke(this, e));
}
diff --git a/UnityPackage/Runtime/Transports/RudpTransport/RudpConnection.cs b/UnityPackage/Runtime/Transports/RudpTransport/RudpConnection.cs
index 56846fe6..5cf6a95d 100644
--- a/UnityPackage/Runtime/Transports/RudpTransport/RudpConnection.cs
+++ b/UnityPackage/Runtime/Transports/RudpTransport/RudpConnection.cs
@@ -3,6 +3,7 @@
// Copyright (c) 2021 Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub: https://github.com/tom-weiland/RiptideNetworking/blob/main/LICENSE.md
+using RiptideNetworking.Utils;
using System;
using System.Net;
@@ -58,13 +59,13 @@ internal RudpConnection(RudpServer server, IPEndPoint endPoint, ushort id)
}
/// Cleans up local objects on disconnection.
- internal void Disconnect()
+ internal void LocalDisconnect()
{
connectionState = ConnectionState.notConnected;
lock (Peer.PendingMessages)
{
- foreach (RudpPeer.PendingMessage pendingMessage in Peer.PendingMessages.Values)
+ foreach (PendingMessage pendingMessage in Peer.PendingMessages.Values)
pendingMessage.Clear(false);
Peer.PendingMessages.Clear();
@@ -149,7 +150,7 @@ internal void HandleWelcomeReceived(Message message)
ushort id = message.GetUShort();
if (Id != id)
- RiptideLogger.Log(server.LogName, $"Client has assumed ID {id} instead of {Id}!");
+ RiptideLogger.Log(LogType.error, server.LogName, $"Client has assumed ID {id} instead of {Id}!");
connectionState = ConnectionState.connected;
server.OnClientConnected(RemoteEndPoint, new ServerClientConnectedEventArgs(this));
diff --git a/UnityPackage/Runtime/Transports/RudpTransport/RudpListener.cs b/UnityPackage/Runtime/Transports/RudpTransport/RudpListener.cs
index 87ab1f22..e4225522 100644
--- a/UnityPackage/Runtime/Transports/RudpTransport/RudpListener.cs
+++ b/UnityPackage/Runtime/Transports/RudpTransport/RudpListener.cs
@@ -3,7 +3,7 @@
// Copyright (c) 2021 Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub: https://github.com/tom-weiland/RiptideNetworking/blob/main/LICENSE.md
-using RiptideNetworking.Transports.Utils;
+using RiptideNetworking.Utils;
using System;
using System.Net;
using System.Net.Sockets;
@@ -14,8 +14,6 @@ namespace RiptideNetworking.Transports.RudpTransport
/// Provides base sending & receiving functionality for and .
public abstract class RudpListener
{
- ///
- public bool ShouldOutputInfoLogs { get; set; } = true;
/// The name to use when logging messages via .
public readonly string LogName;
@@ -28,6 +26,8 @@ public abstract class RudpListener
private Socket socket;
/// Whether or not we are currently listening for incoming data.
private bool isListening = false;
+ /// The buffer that incoming data is received into.
+ private byte[] receiveBuffer;
/// Handles initial setup.
/// The name to use when logging messages via .
@@ -49,10 +49,10 @@ protected void StartListening(ushort port = 0)
{
Message.IncreasePoolCount();
- IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, port);
- socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ IPEndPoint localEndPoint = new IPEndPoint(IPAddress.IPv6Any, port);
+ socket = new Socket(SocketType.Dgram, ProtocolType.Udp);
socket.Bind(localEndPoint);
-
+
new Thread(new ThreadStart(Receive)).Start();
}
@@ -74,32 +74,25 @@ protected void StopListening()
private void Receive()
{
EndPoint bufferEndPoint = new IPEndPoint(socket.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, 0);
+ receiveBuffer = new byte[Message.MaxMessageSize + RiptideConverter.ushortLength];
isListening = true;
while (isListening)
{
int byteCount;
- Message message = null;
-
+
try
{
if (socket.Available == 0 && !socket.Poll(ReceivePollingTime, SelectMode.SelectRead))
continue;
- message = Message.Create();
- byteCount = socket.ReceiveFrom(message.Bytes, 0, message.Bytes.Length, SocketFlags.None, ref bufferEndPoint);
+ byteCount = socket.ReceiveFrom(receiveBuffer, SocketFlags.None, ref bufferEndPoint);
if (byteCount < 1)
- {
- message.Release();
continue;
- }
}
catch (SocketException ex)
{
- if (message != null)
- message.Release();
-
switch (ex.SocketErrorCode)
{
case SocketError.Interrupted:
@@ -116,40 +109,46 @@ private void Receive()
}
catch (ObjectDisposedException)
{
- if (message != null)
- message.Release();
-
return;
}
catch (NullReferenceException)
{
- if (message != null)
- message.Release();
-
return;
}
- PrepareToHandle(message, byteCount, (IPEndPoint)bufferEndPoint);
+ PrepareToHandle(byteCount, (IPEndPoint)bufferEndPoint);
}
}
/// Takes a received message and prepares it to be handled.
- /// The message that was received.
/// The length of the contents of message.
/// The endpoint from which the message was received.
- private void PrepareToHandle(Message message, int length, IPEndPoint remoteEndPoint)
+ private void PrepareToHandle(int length, IPEndPoint remoteEndPoint)
{
- HeaderType messageHeader = message.PrepareForUse((ushort)length);
+ HeaderType messageHeader = (HeaderType)receiveBuffer[0];
if (!ShouldHandleMessageFrom(remoteEndPoint, messageHeader))
return;
+ Message message = Message.Create();
+ message.Bytes[0] = receiveBuffer[0];
+ message.PrepareForUse((ushort)length);
+
if (message.SendMode == MessageSendMode.reliable)
{
- if (message.UnreadLength >= 2) // Reliable messages have a 3 byte header (one of which we've already read out) so don't handle anything with less than that
- ReliableHandle(message, remoteEndPoint, messageHeader);
+ if (length > 3) // Only bother with the array copy if there are more than 3 bytes in the packet (3 or less means no payload for a reliably sent packet)
+ Array.Copy(receiveBuffer, 3, message.Bytes, 1, length - 3);
+ else if (length < 3) // Reliable messages have a 3 byte header, if there aren't that many bytes in the packet don't handle it
+ return;
+
+ ReliableHandle(messageHeader, RiptideConverter.ToUShort(receiveBuffer, 1), message, remoteEndPoint);
}
else
+ {
+ if (length > 1) // Only bother with the array copy if there is more than 1 byte in the packet (1 or less means no payload for a reliably sent packet)
+ Array.Copy(receiveBuffer, 1, message.Bytes, 1, length - 1);
+
Handle(message, remoteEndPoint, messageHeader);
+ }
}
/// Determines whether or not to handle a message from a specific remote endpoint.
@@ -159,20 +158,21 @@ private void PrepareToHandle(Message message, int length, IPEndPoint remoteEndPo
protected abstract bool ShouldHandleMessageFrom(IPEndPoint endPoint, HeaderType messageHeader);
/// Handles the given reliably sent message.
+ /// The header of the message.
+ /// The sequence ID of the message.
/// The message that was received.
/// The endpoint from which the message was received.
- /// The header of the message.
- protected abstract void ReliableHandle(Message message, IPEndPoint fromEndPoint, HeaderType messageHeader);
+ protected abstract void ReliableHandle(HeaderType messageHeader, ushort sequenceId, Message message, IPEndPoint fromEndPoint);
/// Handles the given reliably sent message.
+ /// The header of the message.
+ /// The sequence ID of the message.
/// The message that was received.
/// The endpoint from which the message was received.
- /// The header of the message.
/// The lockable values which are used to inform the other end of which messages we've received.
- internal void ReliableHandle(Message message, IPEndPoint fromEndPoint, HeaderType messageHeader, SendLockables lockables)
+ internal void ReliableHandle(HeaderType messageHeader, ushort sequenceId, Message message, IPEndPoint fromEndPoint, SendLockables lockables)
{
- ushort sequenceId = message.GetUShort();
-
+ bool shouldHandle = true;
lock (lockables)
{
// Update acks
@@ -180,50 +180,78 @@ internal void ReliableHandle(Message message, IPEndPoint fromEndPoint, HeaderTyp
if (sequenceGap > 0)
{
// The received sequence ID is newer than the previous one
- lockables.AcksBitfield <<= sequenceGap; // Shift the bits left to make room for the latest remote sequence ID
- ushort seqIdBit = (ushort)(1 << sequenceGap - 1); // Calculate which bit corresponds to the sequence ID and set it to 1
- if ((lockables.AcksBitfield & seqIdBit) == 0)
+ if (sequenceGap > 64)
+ RiptideLogger.Log(LogType.warning, LogName, $"The gap between received sequence IDs was very large ({sequenceGap})! If the connection's packet loss, latency, or your send rate of reliable messages increases much further, sequence IDs may begin falling outside the bounds of the duplicate filter.");
+
+ lockables.DuplicateFilterBitfield <<= sequenceGap;
+ if (sequenceGap <= 16)
{
- // If we haven't received this packet before
- lockables.AcksBitfield |= seqIdBit; // Set the bit corresponding to the sequence ID to 1 because we received that ID
+ ulong shiftedBits = (ulong)lockables.AcksBitfield << sequenceGap;
+ lockables.AcksBitfield = (ushort)shiftedBits; // Give the acks bitfield the first 2 bytes of the shifted bits
+ lockables.DuplicateFilterBitfield |= shiftedBits >> 16; // OR the last 6 bytes worth of the shifted bits into the duplicate filter bitfield
+
+ shouldHandle = UpdateAcksBitfield(sequenceGap, lockables);
lockables.LastReceivedSeqId = sequenceId;
- SendAck(sequenceId, fromEndPoint);
}
- else
+ else if (sequenceGap <= 80)
{
- SendAck(sequenceId, fromEndPoint);
- return; // Message was a duplicate, don't handle it
+ ulong shiftedBits = (ulong)lockables.AcksBitfield << (sequenceGap - 16);
+ lockables.AcksBitfield = 0; // Reset the acks bitfield as all its bits are being moved to the duplicate filter bitfield
+ lockables.DuplicateFilterBitfield |= shiftedBits; // OR the shifted bits into the duplicate filter bitfield
+
+ shouldHandle = UpdateDuplicateFilterBitfield(sequenceGap, lockables);
}
}
else if (sequenceGap < 0)
{
// The received sequence ID is older than the previous one (out of order message)
sequenceGap = -sequenceGap; // Make sequenceGap positive
- if (sequenceGap > 16) // If it's an old packet and its sequence ID doesn't fall within the bitfield's value range anymore
- SendAck(sequenceId, fromEndPoint); // TODO: store a larger bitfield locally to do a better job of filtering out old duplicates
- else
- {
- ushort seqIdBit = (ushort)(1 << sequenceGap - 1); // Calculate which bit corresponds to the sequence ID and set it to 1
- if ((lockables.AcksBitfield & seqIdBit) == 0) // If we haven't received this packet before
- {
- lockables.AcksBitfield |= seqIdBit; // Set the bit corresponding to the sequence ID to 1 because we received that ID
- SendAck(sequenceId, fromEndPoint);
- }
- else
- {
- SendAck(sequenceId, fromEndPoint);
- return; // Message was a duplicate, don't handle it
- }
- }
+ if (sequenceGap <= 16) // If the message's sequence ID still falls within the ack bitfield's value range
+ shouldHandle = UpdateAcksBitfield(sequenceGap, lockables);
+ else if (sequenceGap <= 80) // If it's an "old" message and its sequence ID doesn't fall within the ack bitfield's value range anymore (but it falls in the range of the duplicate filter)
+ shouldHandle = UpdateDuplicateFilterBitfield(sequenceGap, lockables);
}
else // The received sequence ID is the same as the previous one (duplicate message)
- {
- SendAck(sequenceId, fromEndPoint);
- return; // Message was a duplicate, don't handle it
- }
+ shouldHandle = false;
}
- Handle(message, fromEndPoint, messageHeader);
+ SendAck(sequenceId, fromEndPoint);
+ if (shouldHandle)
+ Handle(message, fromEndPoint, messageHeader);
+ }
+
+ /// Updates the acks bitfield and determines whether or not to handle the message.
+ /// The gap between the newly received sequence ID and the previously last received sequence ID.
+ /// The lockable values which are used to inform the other end of which messages we've received.
+ /// Whether or not the message should be handled, based on whether or not it's a duplicate.
+ private bool UpdateAcksBitfield(int sequenceGap, SendLockables lockables)
+ {
+ ushort seqIdBit = (ushort)(1 << sequenceGap - 1); // Calculate which bit corresponds to the sequence ID and set it to 1
+ if ((lockables.AcksBitfield & seqIdBit) == 0)
+ {
+ // If we haven't received this message before
+ lockables.AcksBitfield |= seqIdBit; // Set the bit corresponding to the sequence ID to 1 because we received that ID
+ return true; // Message was "new", handle it
+ }
+ else // If we have received this message before
+ return false; // Message was a duplicate, don't handle it
+ }
+
+ /// Updates the duplicate filter bitfield and determines whether or not to handle the message.
+ /// The gap between the newly received sequence ID and the previously last received sequence ID.
+ /// The lockable values which are used to inform the other end of which messages we've received.
+ /// Whether or not the message should be handled, based on whether or not it's a duplicate.
+ private bool UpdateDuplicateFilterBitfield(int sequenceGap, SendLockables lockables)
+ {
+ ulong seqIdBit = (ulong)1 << (sequenceGap - 1 - 16); // Calculate which bit corresponds to the sequence ID and set it to 1
+ if ((lockables.DuplicateFilterBitfield & seqIdBit) == 0)
+ {
+ // If we haven't received this message before
+ lockables.DuplicateFilterBitfield |= seqIdBit; // Set the bit corresponding to the sequence ID to 1 because we received that ID
+ return true; // Message was "new", handle it
+ }
+ else // If we have received this message before
+ return false; // Message was a duplicate, don't handle it
}
/// Handles the given message.
@@ -280,12 +308,7 @@ internal void SendReliable(Message message, IPEndPoint toEndPoint, RudpPeer peer
return;
ushort sequenceId = peer.NextSequenceId; // Get the next sequence ID
- RudpPeer.PendingMessage pendingMessage = new RudpPeer.PendingMessage(peer, sequenceId, message, toEndPoint);
- lock (peer.PendingMessages)
- {
- peer.PendingMessages.Add(sequenceId, pendingMessage);
- pendingMessage.TrySend();
- }
+ PendingMessage.CreateAndSend(peer, sequenceId, message, toEndPoint);
}
/// Sends an acknowledgement for a sequence ID to a specific endpoint.
diff --git a/UnityPackage/Runtime/Transports/RudpTransport/RudpPeer.cs b/UnityPackage/Runtime/Transports/RudpTransport/RudpPeer.cs
index b6e58baa..d04c2cae 100644
--- a/UnityPackage/Runtime/Transports/RudpTransport/RudpPeer.cs
+++ b/UnityPackage/Runtime/Transports/RudpTransport/RudpPeer.cs
@@ -3,12 +3,9 @@
// Copyright (c) 2021 Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub: https://github.com/tom-weiland/RiptideNetworking/blob/main/LICENSE.md
-using RiptideNetworking.Transports.Utils;
using System;
using System.Collections.Generic;
-using System.Net;
using System.Threading;
-using Timer = System.Timers.Timer;
namespace RiptideNetworking.Transports.RudpTransport
{
@@ -36,14 +33,11 @@ internal short RTT
private short _rtt = -1;
///
internal short SmoothRTT { get; set; } = -1;
-
- /// The multiplier used to determine how long to wait before resending a pending message.
- protected readonly float retryTimeMultiplier = 1.2f;
+ /// The whose socket to use when sending data.
+ internal readonly RudpListener Listener;
/// The last used sequence ID.
private int lastSequenceId;
- /// The whose socket to use when sending data.
- private readonly RudpListener listener;
/// A with the left-most bit set to 1.
private const ushort LeftBit = 1 << 15;
@@ -51,7 +45,7 @@ internal short RTT
/// The whose socket to use when sending data.
internal RudpPeer(RudpListener rudpListener)
{
- listener = rudpListener;
+ Listener = rudpListener;
SendLockables = new SendLockables();
ReceiveLockables = new ReceiveLockables();
}
@@ -139,125 +133,5 @@ internal static int GetSequenceGap(ushort seqId1, ushort seqId2)
else // Difference is big, meaning sequence IDs are far apart
return (seqId1 <= 32768 ? ushort.MaxValue + 1 + seqId1 : seqId1) - (seqId2 <= 32768 ? ushort.MaxValue + 1 + seqId2 : seqId2);
}
-
- /// Represents a currently pending reliably sent message whose delivery has not been acknowledged yet.
- internal class PendingMessage
- {
- /// The to use to send (and resend) the pending message.
- private readonly RudpPeer peer;
- /// The intended destination endpoint of the message.
- private readonly IPEndPoint remoteEndPoint;
- /// The sequence ID of the message.
- private readonly ushort sequenceId;
- /// The contents of the message.
- private readonly byte[] data;
- /// How often to try sending the message before giving up.
- private readonly int maxSendAttempts;
- /// How many send attempts have been made so far.
- private byte sendAttempts;
- /// The time of the latest send attempt.
- private DateTime lastSendTime;
- /// The timer responsible for triggering a resend, if all else fails (like acks getting lost or redundant acks not being updated fast enough).
- private readonly Timer retryTimer;
- /// Whether the pending message has been cleared or not.
- private bool wasCleared;
-
- /// Handles initial setup.
- /// The to use to send (and resend) the pending message.
- /// The sequence ID of the message.
- /// The message that is being sent reliably.
- /// The intended destination endpoint of the message.
- internal PendingMessage(RudpPeer peer, ushort sequenceId, Message message, IPEndPoint toEndPoint)
- {
- this.peer = peer;
- this.sequenceId = sequenceId;
-
- data = new byte[message.WrittenLength + RiptideConverter.ushortLength];
- data[0] = message.Bytes[0]; // Copy message header
- RiptideConverter.FromUShort(sequenceId, data, 1); // Insert sequence ID
- Array.Copy(message.Bytes, 1, data, 3, message.WrittenLength - 1); // Copy the rest of the message
-
- remoteEndPoint = toEndPoint;
- maxSendAttempts = message.MaxSendAttempts;
- sendAttempts = 0;
-
- retryTimer = new Timer();
- retryTimer.Elapsed += (s, e) => RetrySend();
- retryTimer.AutoReset = false;
- }
-
- /// Resends the message.
- internal void RetrySend()
- {
- lock (this) // Make sure we don't try resending the message while another thread is clearing it because it was delivered
- {
- if (!wasCleared)
- {
- if (lastSendTime.AddMilliseconds(peer.SmoothRTT < 0 ? 25 : peer.SmoothRTT * 0.5f) <= DateTime.UtcNow) // Avoid triggering a resend if the latest resend was less than half a RTT ago
- TrySend();
- else
- {
- retryTimer.Start();
- retryTimer.Interval = (peer.SmoothRTT < 0 ? 50 : Math.Max(10, peer.SmoothRTT * peer.retryTimeMultiplier));
- }
- }
- }
- }
-
- /// Attempts to send the message.
- internal void TrySend()
- {
- if (sendAttempts >= maxSendAttempts)
- {
- // Send attempts exceeds max send attempts, so give up
- if (peer.listener.ShouldOutputInfoLogs)
- {
- HeaderType headerType = (HeaderType)data[0];
- if (headerType == HeaderType.reliable)
- {
-#if BIG_ENDIAN
- ushort messageId = (ushort)(data[4] | (data[3] << 8));
-#else
- ushort messageId = (ushort)(data[3] | (data[4] << 8));
-#endif
-
- RiptideLogger.Log(peer.listener.LogName, $"No ack received for {headerType} message (ID: {messageId}) after {sendAttempts} attempt(s), delivery may have failed!");
- }
- else
- RiptideLogger.Log(peer.listener.LogName, $"No ack received for internal {headerType} message after {sendAttempts} attempt(s), delivery may have failed!");
- }
-
- Clear();
- return;
- }
-
- peer.listener.Send(data, remoteEndPoint);
-
- lastSendTime = DateTime.UtcNow;
- sendAttempts++;
-
- retryTimer.Start();
- retryTimer.Interval = peer.SmoothRTT < 0 ? 50 : Math.Max(10, peer.SmoothRTT * peer.retryTimeMultiplier);
- }
-
- /// Clears and removes the message from the dictionary of pending messages.
- /// Whether or not to remove the message from .
- internal void Clear(bool shouldRemoveFromDictionary = true)
- {
- lock (this)
- {
- if (!wasCleared)
- {
- if (shouldRemoveFromDictionary)
- lock (peer.PendingMessages)
- peer.PendingMessages.Remove(sequenceId);
-
- retryTimer.Stop();
- retryTimer.Dispose();
- wasCleared = true;
- }
- }
- }
- }
}
}
\ No newline at end of file
diff --git a/UnityPackage/Runtime/Transports/RudpTransport/RudpServer.cs b/UnityPackage/Runtime/Transports/RudpTransport/RudpServer.cs
index fe68cac9..f4744356 100644
--- a/UnityPackage/Runtime/Transports/RudpTransport/RudpServer.cs
+++ b/UnityPackage/Runtime/Transports/RudpTransport/RudpServer.cs
@@ -3,7 +3,7 @@
// Copyright (c) 2021 Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub: https://github.com/tom-weiland/RiptideNetworking/blob/main/LICENSE.md
-using RiptideNetworking.Transports.Utils;
+using RiptideNetworking.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -27,9 +27,23 @@ public class RudpServer : RudpListener, IServer
///
public ushort MaxClientCount { get; private set; }
///
- public int ClientCount => clients.Count;
+ public int ClientCount
+ {
+ get
+ {
+ lock (clients)
+ return clients != null ? clients.Count : 0;
+ }
+ }
///
- public IConnectionInfo[] Clients => clients.Values.ToArray();
+ public IConnectionInfo[] Clients
+ {
+ get
+ {
+ lock (clients)
+ return clients != null ? clients.Values.ToArray() : new IConnectionInfo[0];
+ }
+ }
/// The time (in milliseconds) after which to disconnect a client without a heartbeat.
public ushort ClientTimeoutTime { get; set; } = 5000;
/// The interval (in milliseconds) at which heartbeats are to be expected from clients.
@@ -76,29 +90,27 @@ public void Start(ushort port, ushort maxClientCount)
StartListening(port);
- heartbeatTimer = new Timer(Heartbeat, null, 0, ClientHeartbeatInterval);
-
- if (ShouldOutputInfoLogs)
- RiptideLogger.Log(LogName, $"Started on port {port}.");
+ heartbeatTimer = new Timer((o) => Heartbeat(), null, 0, ClientHeartbeatInterval);
+ RiptideLogger.Log(LogType.info, LogName, $"Started on port {port}.");
}
-
/// Checks if clients have timed out. Called by .
- private void Heartbeat(object state)
+ private void Heartbeat()
{
- lock (clients)
+ receiveActionQueue.Add(() =>
{
- foreach (RudpConnection client in clients.Values)
+ lock (clients)
{
- if (client.HasTimedOut)
- timedOutClients.Add(client.RemoteEndPoint);
+ foreach (RudpConnection client in clients.Values)
+ if (client.HasTimedOut)
+ timedOutClients.Add(client.RemoteEndPoint);
}
foreach (IPEndPoint clientEndPoint in timedOutClients)
- HandleDisconnect(clientEndPoint); // Disconnect the client
+ HandleDisconnect(clientEndPoint); // Disconnect the clients
timedOutClients.Clear();
- }
+ });
}
@@ -113,7 +125,7 @@ protected override bool ShouldHandleMessageFrom(IPEndPoint endPoint, HeaderType
if (messageHeader != HeaderType.connect) // It's not a connect message, so handle it
return true;
}
- else if (clients.Count < MaxClientCount)
+ else if (ClientCount < MaxClientCount)
{
// Client is not yet connected and the server has capacity
if (messageHeader == HeaderType.connect) // It's a connect message, which doesn't need to be handled like other messages
@@ -125,8 +137,7 @@ protected override bool ShouldHandleMessageFrom(IPEndPoint endPoint, HeaderType
else
{
// Server is full
- if (ShouldOutputInfoLogs)
- RiptideLogger.Log(LogName, $"Server is full! Rejecting connection from {endPoint}.");
+ RiptideLogger.Log(LogType.info, LogName, $"Server is full! Rejecting connection from {endPoint}.");
}
return false;
@@ -136,81 +147,76 @@ protected override bool ShouldHandleMessageFrom(IPEndPoint endPoint, HeaderType
///
protected override void Handle(Message message, IPEndPoint fromEndPoint, HeaderType messageHeader)
{
- lock (clients)
- {
- if (!clients.TryGetValue(fromEndPoint, out RudpConnection client))
- return;
+ if (!TryGetClient(fromEndPoint, out RudpConnection client))
+ return;
#if DETAILED_LOGGING
- if (messageHeader != HeaderType.reliable && messageHeader != HeaderType.unreliable)
- RiptideLogger.Log(LogName, $"Received {messageHeader} message from {fromEndPoint}.");
-
- ushort messageId = message.PeekUShort();
- if (messageHeader == HeaderType.reliable)
- RiptideLogger.Log(LogName, $"Received reliable message (ID: {messageId}) from {fromEndPoint}.");
- else if (messageHeader == HeaderType.unreliable)
- RiptideLogger.Log(LogName, $"Received unreliable message (ID: {messageId}) from {fromEndPoint}.");
+ if (messageHeader != HeaderType.reliable && messageHeader != HeaderType.unreliable)
+ RiptideLogger.Log(LogName, $"Received {messageHeader} message from {fromEndPoint}.");
+
+ ushort messageId = message.PeekUShort();
+ if (messageHeader == HeaderType.reliable)
+ RiptideLogger.Log(LogName, $"Received reliable message (ID: {messageId}) from {fromEndPoint}.");
+ else if (messageHeader == HeaderType.unreliable)
+ RiptideLogger.Log(LogName, $"Received unreliable message (ID: {messageId}) from {fromEndPoint}.");
#endif
- switch (messageHeader)
- {
- // User messages
- case HeaderType.unreliable:
- case HeaderType.reliable:
- receiveActionQueue.Add(() =>
- {
- // This block may execute on a different thread, so we double check if the client is still in the dictionary in case they disconnected
- lock (clients)
- {
- if (clients.TryGetValue(fromEndPoint, out RudpConnection client2))
- OnMessageReceived(new ServerMessageReceivedEventArgs(client2.Id, message.GetUShort(), message));
- }
-
- message.Release();
- });
- return;
-
- // Internal messages
- case HeaderType.ack:
- client.HandleAck(message);
- break;
- case HeaderType.ackExtra:
- client.HandleAckExtra(message);
- break;
- case HeaderType.connect:
- // Handled in ShouldHandleMessageFrom method
- break;
- case HeaderType.heartbeat:
- client.HandleHeartbeat(message);
- break;
- case HeaderType.welcome:
- client.HandleWelcomeReceived(message);
- break;
- case HeaderType.clientConnected:
- case HeaderType.clientDisconnected:
- break;
- case HeaderType.disconnect:
- HandleDisconnect(fromEndPoint);
- break;
- default:
- RiptideLogger.Log(LogName, $"Unknown message header type '{messageHeader}'! Discarding {message.WrittenLength} bytes received from {fromEndPoint}.");
- break;
- }
+ switch (messageHeader)
+ {
+ // User messages
+ case HeaderType.unreliable:
+ case HeaderType.reliable:
+ receiveActionQueue.Add(() =>
+ {
+ // This block may execute on a different thread, so we double check if the client is still in the dictionary in case they disconnected
+ if (TryGetClient(fromEndPoint, out RudpConnection client2))
+ OnMessageReceived(new ServerMessageReceivedEventArgs(client2.Id, message.GetUShort(), message));
- message.Release();
+ message.Release();
+ });
+ return;
+
+ // Internal messages
+ case HeaderType.ack:
+ client.HandleAck(message);
+ break;
+ case HeaderType.ackExtra:
+ client.HandleAckExtra(message);
+ break;
+ case HeaderType.connect:
+ // Handled in ShouldHandleMessageFrom method
+ break;
+ case HeaderType.heartbeat:
+ client.HandleHeartbeat(message);
+ break;
+ case HeaderType.welcome:
+ client.HandleWelcomeReceived(message);
+ break;
+ case HeaderType.clientConnected:
+ case HeaderType.clientDisconnected:
+ break;
+ case HeaderType.disconnect:
+ HandleDisconnect(fromEndPoint);
+ break;
+ default:
+ RiptideLogger.Log(LogType.warning, LogName, $"Unknown message header type '{messageHeader}'! Discarding {message.WrittenLength} bytes received from {fromEndPoint}.");
+ break;
}
+
+ message.Release();
}
///
- protected override void ReliableHandle(Message message, IPEndPoint fromEndPoint, HeaderType messageHeader)
+ protected override void ReliableHandle(HeaderType messageHeader, ushort sequenceId, Message message, IPEndPoint fromEndPoint)
{
- ReliableHandle(message, fromEndPoint, messageHeader, clients[fromEndPoint].SendLockables);
+ if (TryGetClient(fromEndPoint, out RudpConnection client))
+ ReliableHandle(messageHeader, sequenceId, message, fromEndPoint, client.SendLockables);
}
///
public void Send(Message message, ushort toClientId, bool shouldRelease = true)
{
- if (clients.TryGetValue(toClientId, out RudpConnection toClient))
+ if (TryGetClient(toClientId, out RudpConnection toClient))
Send(message, toClient, false);
if (shouldRelease)
@@ -279,26 +285,25 @@ public void SendToAll(Message message, ushort exceptToClientId, bool shouldRelea
///
public void DisconnectClient(ushort clientId)
{
- if (clients.TryGetValue(clientId, out RudpConnection client))
+ if (TryGetClient(clientId, out RudpConnection client))
{
SendDisconnect(client.Id);
- if (ShouldOutputInfoLogs)
- RiptideLogger.Log(LogName, $"Kicked {client.RemoteEndPoint} (ID: {client.Id}).");
+ RiptideLogger.Log(LogType.info, LogName, $"Kicked {client.RemoteEndPoint.ToStringBasedOnIPFormat()} (ID: {client.Id}).");
LocalDisconnect(client);
- availableClientIds.Add(client.Id);
}
else
- RiptideLogger.Log(LogName, $"Failed to kick {client.RemoteEndPoint} because they weren't connected!");
+ RiptideLogger.Log(LogType.warning, LogName, $"Failed to kick {client.RemoteEndPoint} because they weren't connected!");
}
private void LocalDisconnect(RudpConnection client)
{
- client.Disconnect();
+ client.LocalDisconnect();
lock (clients)
clients.Remove(client.Id, client.RemoteEndPoint);
OnClientDisconnected(new ClientDisconnectedEventArgs(client.Id));
+ availableClientIds.Add(client.Id);
}
///
@@ -316,8 +321,7 @@ public void Shutdown()
heartbeatTimer.Dispose();
StopListening();
- if (ShouldOutputInfoLogs)
- RiptideLogger.Log(LogName, "Server stopped.");
+ RiptideLogger.Log(LogType.info, LogName, "Server stopped.");
}
/// Initializes available client IDs.
@@ -340,11 +344,23 @@ private ushort GetAvailableClientId()
}
else
{
- RiptideLogger.Log(LogName, "No available client IDs, assigned 0!");
+ RiptideLogger.Log(LogType.error, LogName, "No available client IDs, assigned 0!");
return 0;
}
}
+ private bool TryGetClient(ushort clientId, out RudpConnection client)
+ {
+ lock (clients)
+ return clients.TryGetValue(clientId, out client);
+ }
+
+ private bool TryGetClient(IPEndPoint fromEndPoint, out RudpConnection client)
+ {
+ lock (clients)
+ return clients.TryGetValue(fromEndPoint, out client);
+ }
+
#region Messages
/// Sends a disconnect message.
/// The client to send the disconnect message to.
@@ -356,18 +372,16 @@ private void SendDisconnect(ushort clientId)
///
protected override void SendAck(ushort forSeqId, IPEndPoint toEndPoint)
{
- clients[toEndPoint].SendAck(forSeqId);
+ if (TryGetClient(toEndPoint, out RudpConnection client))
+ client.SendAck(forSeqId);
}
/// Handles a disconnect message.
/// The endpoint from which the disconnect message was received.
private void HandleDisconnect(IPEndPoint fromEndPoint)
{
- if (clients.TryGetValue(fromEndPoint, out RudpConnection client))
- {
+ if (TryGetClient(fromEndPoint, out RudpConnection client))
LocalDisconnect(client);
- availableClientIds.Add(client.Id);
- }
}
/// Sends a client connected message.
@@ -375,7 +389,7 @@ private void HandleDisconnect(IPEndPoint fromEndPoint)
/// The ID of the newly connected client.
private void SendClientConnected(IPEndPoint endPoint, ushort id)
{
- if (clients.Count <= 1)
+ if (ClientCount <= 1)
return; // We don't send this to the newly connected client anyways, so don't even bother creating a message if he is the only one connected
Message message = Message.Create(HeaderType.clientConnected, 25);
@@ -410,11 +424,9 @@ private void SendClientDisconnected(ushort id)
/// The event args to invoke the event with.
internal void OnClientConnected(IPEndPoint clientEndPoint, ServerClientConnectedEventArgs e)
{
- if (ShouldOutputInfoLogs)
- RiptideLogger.Log(LogName, $"{clientEndPoint} connected successfully! Client ID: {e.Client.Id}");
+ RiptideLogger.Log(LogType.info, LogName, $"{clientEndPoint.ToStringBasedOnIPFormat()} connected successfully! Client ID: {e.Client.Id}");
receiveActionQueue.Add(() => ClientConnected?.Invoke(this, e));
-
SendClientConnected(clientEndPoint, e.Client.Id);
}
@@ -429,11 +441,9 @@ private void OnMessageReceived(ServerMessageReceivedEventArgs e)
/// The event args to invoke the event with.
private void OnClientDisconnected(ClientDisconnectedEventArgs e)
{
- if (ShouldOutputInfoLogs)
- RiptideLogger.Log(LogName, $"Client {e.Id} disconnected.");
+ RiptideLogger.Log(LogType.info, LogName, $"Client {e.Id} disconnected.");
receiveActionQueue.Add(() => ClientDisconnected?.Invoke(this, e));
-
SendClientDisconnected(e.Id);
}
#endregion
diff --git a/UnityPackage/Runtime/Transports/Utilities.meta b/UnityPackage/Runtime/Utils.meta
similarity index 77%
rename from UnityPackage/Runtime/Transports/Utilities.meta
rename to UnityPackage/Runtime/Utils.meta
index 50583dfb..c3b534e7 100644
--- a/UnityPackage/Runtime/Transports/Utilities.meta
+++ b/UnityPackage/Runtime/Utils.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 71ff5486e59def640a908e447c210a18
+guid: c2b0d22058a6067428c40f9ff2476632
folderAsset: yes
DefaultImporter:
externalObjects: {}
diff --git a/UnityPackage/Runtime/Transports/Utilities/ActionQueue.cs b/UnityPackage/Runtime/Utils/ActionQueue.cs
similarity index 77%
rename from UnityPackage/Runtime/Transports/Utilities/ActionQueue.cs
rename to UnityPackage/Runtime/Utils/ActionQueue.cs
index d87b5ea3..4638a50d 100644
--- a/UnityPackage/Runtime/Transports/Utilities/ActionQueue.cs
+++ b/UnityPackage/Runtime/Utils/ActionQueue.cs
@@ -6,7 +6,7 @@
using System;
using System.Collections.Generic;
-namespace RiptideNetworking.Transports.Utils
+namespace RiptideNetworking.Utils
{
/// Provides functionality for queueing methods for later execution from a chosen thread.
public class ActionQueue
@@ -30,7 +30,7 @@ public void Add(Action action)
{
if (action == null)
{
- RiptideLogger.Log(LogName, "No action to execute!");
+ RiptideLogger.Log(LogType.error, LogName, "No action to execute!");
return;
}
@@ -42,16 +42,16 @@ public void Add(Action action)
}
/// Executes all actions in the queue on the calling thread.
+ /// This method should only be called from a single thread in the application.
public void ExecuteAll()
{
if (hasActionToExecute)
{
- // If there's an action in the queue
- executionQueueCopy.Clear(); // Clear the old actions from the copied queue
+ executionQueueCopy.Clear();
lock (executionQueue)
{
- executionQueueCopy.AddRange(executionQueue); // Copy actions from the queue to the copied queue
- executionQueue.Clear(); // Clear the actions from the queue
+ executionQueueCopy.AddRange(executionQueue);
+ executionQueue.Clear();
hasActionToExecute = false;
}
@@ -60,5 +60,15 @@ public void ExecuteAll()
executionQueueCopy[i]();
}
}
+
+ /// Clears all actions in the queue without executing them.
+ public void Clear()
+ {
+ lock (executionQueue)
+ {
+ executionQueue.Clear();
+ hasActionToExecute = false;
+ }
+ }
}
}
diff --git a/UnityPackage/Runtime/Transports/Utilities/ActionQueue.cs.meta b/UnityPackage/Runtime/Utils/ActionQueue.cs.meta
similarity index 83%
rename from UnityPackage/Runtime/Transports/Utilities/ActionQueue.cs.meta
rename to UnityPackage/Runtime/Utils/ActionQueue.cs.meta
index 1982bd55..57d92205 100644
--- a/UnityPackage/Runtime/Transports/Utilities/ActionQueue.cs.meta
+++ b/UnityPackage/Runtime/Utils/ActionQueue.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 4577bfe2f8f930f4cb93b80a63ebc80f
+guid: f4582a11cc9b833468202ca11d458077
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/UnityPackage/Runtime/Transports/Utilities/DoubleKeyDictionary.cs b/UnityPackage/Runtime/Utils/DoubleKeyDictionary.cs
similarity index 99%
rename from UnityPackage/Runtime/Transports/Utilities/DoubleKeyDictionary.cs
rename to UnityPackage/Runtime/Utils/DoubleKeyDictionary.cs
index 368b2205..889a627f 100644
--- a/UnityPackage/Runtime/Transports/Utilities/DoubleKeyDictionary.cs
+++ b/UnityPackage/Runtime/Utils/DoubleKeyDictionary.cs
@@ -7,7 +7,7 @@
using System.Collections;
using System.Collections.Generic;
-namespace RiptideNetworking.Transports.Utils
+namespace RiptideNetworking.Utils
{
/// Represents a collection of two keys for each value.
/// Key type 1.
diff --git a/UnityPackage/Runtime/Transports/Utilities/DoubleKeyDictionary.cs.meta b/UnityPackage/Runtime/Utils/DoubleKeyDictionary.cs.meta
similarity index 83%
rename from UnityPackage/Runtime/Transports/Utilities/DoubleKeyDictionary.cs.meta
rename to UnityPackage/Runtime/Utils/DoubleKeyDictionary.cs.meta
index adc2e734..781ab72f 100644
--- a/UnityPackage/Runtime/Transports/Utilities/DoubleKeyDictionary.cs.meta
+++ b/UnityPackage/Runtime/Utils/DoubleKeyDictionary.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: fd5ae0f3c66c13344b8a0bab822e8abc
+guid: 0c300934bf1d837469dcf0c2193750d9
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/UnityPackage/Runtime/Utils/Extensions.cs b/UnityPackage/Runtime/Utils/Extensions.cs
new file mode 100644
index 00000000..79d5d368
--- /dev/null
+++ b/UnityPackage/Runtime/Utils/Extensions.cs
@@ -0,0 +1,23 @@
+
+// This file is provided under The MIT License as part of RiptideNetworking.
+// Copyright (c) 2021 Tom Weiland
+// For additional information please see the included LICENSE.md file or view it on GitHub: https://github.com/tom-weiland/RiptideNetworking/blob/main/LICENSE.md
+
+using System.Net;
+
+namespace RiptideNetworking.Utils
+{
+ /// Contains extension methods for various classes.
+ public static class Extensions
+ {
+ /// Takes an and returns a string containing its IP address and port number, accounting for whether the address is an IPv4 or IPv6.
+ /// A string containing the IP address and port number of the endpoint.
+ public static string ToStringBasedOnIPFormat(this IPEndPoint endPoint)
+ {
+ if (endPoint.Address.IsIPv4MappedToIPv6)
+ return $"{endPoint.Address.MapToIPv4()}:{endPoint.Port}";
+ else
+ return endPoint.ToString();
+ }
+ }
+}
diff --git a/UnityPackage/Runtime/Utils/Extensions.cs.meta b/UnityPackage/Runtime/Utils/Extensions.cs.meta
new file mode 100644
index 00000000..fd0c035a
--- /dev/null
+++ b/UnityPackage/Runtime/Utils/Extensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 600872db1b0e66f418a13dc189791be3
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityPackage/Runtime/Transports/Utilities/RiptideConverter.cs b/UnityPackage/Runtime/Utils/RiptideConverter.cs
similarity index 99%
rename from UnityPackage/Runtime/Transports/Utilities/RiptideConverter.cs
rename to UnityPackage/Runtime/Utils/RiptideConverter.cs
index 630cc7a5..532ec867 100644
--- a/UnityPackage/Runtime/Transports/Utilities/RiptideConverter.cs
+++ b/UnityPackage/Runtime/Utils/RiptideConverter.cs
@@ -7,7 +7,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-namespace RiptideNetworking.Transports.Utils
+namespace RiptideNetworking.Utils
{
/// Provides functionality for converting bytes to various value types and vice versa.
public class RiptideConverter
diff --git a/UnityPackage/Runtime/Transports/Utilities/RiptideConverter.cs.meta b/UnityPackage/Runtime/Utils/RiptideConverter.cs.meta
similarity index 83%
rename from UnityPackage/Runtime/Transports/Utilities/RiptideConverter.cs.meta
rename to UnityPackage/Runtime/Utils/RiptideConverter.cs.meta
index a8c58931..4012fc1c 100644
--- a/UnityPackage/Runtime/Transports/Utilities/RiptideConverter.cs.meta
+++ b/UnityPackage/Runtime/Utils/RiptideConverter.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 11ca6dcc2a234f046a6c29a67ddaae10
+guid: c6253d1a272e91940825189b3969e07d
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/UnityPackage/Runtime/Utils/RiptideLogger.cs b/UnityPackage/Runtime/Utils/RiptideLogger.cs
new file mode 100644
index 00000000..62c48445
--- /dev/null
+++ b/UnityPackage/Runtime/Utils/RiptideLogger.cs
@@ -0,0 +1,130 @@
+
+// This file is provided under The MIT License as part of RiptideNetworking.
+// Copyright (c) 2021 Tom Weiland
+// For additional information please see the included LICENSE.md file or view it on GitHub: https://github.com/tom-weiland/RiptideNetworking/blob/main/LICENSE.md
+
+using System;
+using System.Collections.Generic;
+
+namespace RiptideNetworking.Utils
+{
+ /// Defines log message types.
+ public enum LogType
+ {
+ /// Logs that are used for investigation during development.
+ debug,
+ /// Logs that provide general information about application flow.
+ info,
+ /// Logs that highlight abnormal or unexpected events in the application flow.
+ warning,
+ /// Logs that highlight problematic events in the application flow which will cause unexpected behavior if not planned for.
+ error
+ }
+
+ /// Provides functionality for logging messages.
+ public class RiptideLogger
+ {
+ /// Whether or not messages will be logged.
+ public static bool IsDebugLoggingEnabled => logMethods.ContainsKey(LogType.debug);
+ /// Whether or not messages will be logged.
+ public static bool IsInfoLoggingEnabled => logMethods.ContainsKey(LogType.info);
+ /// Whether or not messages will be logged.
+ public static bool IsWarningLoggingEnabled => logMethods.ContainsKey(LogType.warning);
+ /// Whether or not messages will be logged.
+ public static bool IsErrorLoggingEnabled => logMethods.ContainsKey(LogType.error);
+ /// Encapsulates a method used to log messages.
+ /// The message to log.
+ public delegate void LogMethod(string log);
+
+ /// Log methods, accessible by their
+ private static Dictionary logMethods = new Dictionary(4);
+ /// Whether or not to include timestamps when logging messages.
+ private static bool includeTimestamps;
+ /// The format to use for timestamps.
+ private static string timestampFormat;
+
+ /// Initializes with all log types enabled.
+ /// The method to use when logging all types of messages.
+ /// Whether or not to include timestamps when logging messages.
+ /// The format to use for timestamps.
+ public static void Initialize(LogMethod logMethod, bool includeTimestamps, string timestampFormat = "HH:mm:ss") => Initialize(logMethod, logMethod, logMethod, logMethod, includeTimestamps, timestampFormat);
+ /// Initializes with the supplied log methods.
+ /// The method to use when logging debug messages. Set to to disable debug logs.
+ /// The method to use when logging info messages. Set to to disable info logs.
+ /// The method to use when logging warning messages. Set to to disable warning logs.
+ /// The method to use when logging error messages. Set to to disable error logs.
+ /// Whether or not to include timestamps when logging messages.
+ /// The format to use for timestamps.
+ public static void Initialize(LogMethod debugMethod, LogMethod infoMethod, LogMethod warningMethod, LogMethod errorMethod, bool includeTimestamps, string timestampFormat = "HH:mm:ss")
+ {
+ logMethods.Clear();
+
+ if (debugMethod != null)
+ logMethods.Add(LogType.debug, debugMethod);
+ if (infoMethod != null)
+ logMethods.Add(LogType.info, infoMethod);
+ if (warningMethod != null)
+ logMethods.Add(LogType.warning, warningMethod);
+ if (errorMethod != null)
+ logMethods.Add(LogType.error, errorMethod);
+
+ RiptideLogger.includeTimestamps = includeTimestamps;
+ RiptideLogger.timestampFormat = timestampFormat;
+ }
+
+ /// Enables logging for messages of the given .
+ /// The type of message to enable logging for.
+ /// The method to use when logging this type of message.
+ public static void EnableLoggingFor(LogType logType, LogMethod logMethod)
+ {
+ if (logMethods.ContainsKey(logType))
+ logMethods[logType] = logMethod;
+ else
+ logMethods.Add(logType, logMethod);
+ }
+
+ /// Disables logging for messages of the given .
+ /// The type of message to enable logging for.
+ public static void DisableLoggingFor(LogType logType) => logMethods.Remove(logType);
+
+ /// Logs a message.
+ /// The type of log message that is being logged.
+ /// The message to log.
+ public static void Log(LogType logType, string message)
+ {
+ if (logMethods.TryGetValue(logType, out LogMethod logMethod))
+ {
+ if (includeTimestamps)
+ logMethod($"[{GetTimestamp(DateTime.Now)}]: {message}");
+ else
+ logMethod(message);
+ }
+ }
+ /// Logs a message.
+ /// The type of log message that is being logged.
+ /// Who is logging this message.
+ /// The message to log.
+ public static void Log(LogType logType, string logName, string message)
+ {
+ if (logMethods.TryGetValue(logType, out LogMethod logMethod))
+ {
+ if (includeTimestamps)
+ logMethod($"[{GetTimestamp(DateTime.Now)}] ({logName}): {message}");
+ else
+ logMethod($"({logName}): {message}");
+ }
+ }
+
+ /// Converts a object to a formatted timestamp string.
+ /// The time to format.
+ /// The formatted timestamp.
+ private static string GetTimestamp(DateTime time)
+ {
+#if DETAILED_LOGGING
+ return time.ToString("HH:mm:ss:fff");
+#else
+ return time.ToString(timestampFormat);
+#endif
+ }
+ }
+}
diff --git a/UnityPackage/Runtime/RiptideLogger.cs.meta b/UnityPackage/Runtime/Utils/RiptideLogger.cs.meta
similarity index 83%
rename from UnityPackage/Runtime/RiptideLogger.cs.meta
rename to UnityPackage/Runtime/Utils/RiptideLogger.cs.meta
index f91e3712..f57859ba 100644
--- a/UnityPackage/Runtime/RiptideLogger.cs.meta
+++ b/UnityPackage/Runtime/Utils/RiptideLogger.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 44d8a2c258364964e959f3e699e349fc
+guid: d8f696d53cf3eb34db23e651770ffae6
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/UnityPackage/Runtime/RiptideNetworking.asmdef b/UnityPackage/Runtime/tomweiland.riptidenetworking.runtime.asmdef
similarity index 79%
rename from UnityPackage/Runtime/RiptideNetworking.asmdef
rename to UnityPackage/Runtime/tomweiland.riptidenetworking.runtime.asmdef
index ad3c906e..77504d53 100644
--- a/UnityPackage/Runtime/RiptideNetworking.asmdef
+++ b/UnityPackage/Runtime/tomweiland.riptidenetworking.runtime.asmdef
@@ -1,5 +1,6 @@
{
"name": "RiptideNetworking",
+ "rootNamespace": "RiptideNetworking",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
@@ -9,5 +10,5 @@
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
- "noEngineReferences": true
+ "noEngineReferences": false
}
\ No newline at end of file
diff --git a/UnityPackage/Runtime/RiptideNetworking.asmdef.meta b/UnityPackage/Runtime/tomweiland.riptidenetworking.runtime.asmdef.meta
similarity index 76%
rename from UnityPackage/Runtime/RiptideNetworking.asmdef.meta
rename to UnityPackage/Runtime/tomweiland.riptidenetworking.runtime.asmdef.meta
index 1155651d..eaee354a 100644
--- a/UnityPackage/Runtime/RiptideNetworking.asmdef.meta
+++ b/UnityPackage/Runtime/tomweiland.riptidenetworking.runtime.asmdef.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: d43a60f4ef2b3b14ab2a490f0896a13d
+guid: 6cc73ec323001034d9bfacf4b6ed7b48
AssemblyDefinitionImporter:
externalObjects: {}
userData:
diff --git a/UnityPackage/package.json b/UnityPackage/package.json
index 51306795..31173f9c 100644
--- a/UnityPackage/package.json
+++ b/UnityPackage/package.json
@@ -1,23 +1,23 @@
{
- "name": "net.tomweiland.riptide-networking",
- "version": "0.8.2",
- "displayName": "Riptide Networking",
- "description": "Riptide Networking is a lightweight C# networking library primarily designed for use in multiplayer games.",
- "unity": "2019.4",
- "license": "MIT",
- "author": {
- "name": "Tom Weiland",
- "email": "contact@tomweiland.net",
- "url": "https://www.youtube.com/c/TomWeiland"
- },
- "dependencies": {},
- "keywords": [
- "riptide",
- "networking",
- "multiplayer"
- ],
- "repository": {
- "url": "https://github.com/tom-weiland/RiptideNetworking",
- "type": "git"
- }
+ "name": "net.tomweiland.riptidenetworking",
+ "version": "1.0.0",
+ "displayName": "RiptideNetworking",
+ "description": "RiptideNetworking is a lightweight C# networking library primarily designed for use in multiplayer games.",
+ "unity": "2019.4",
+ "license": "MIT",
+ "author": {
+ "name": "Tom Weiland",
+ "email": "contact@tomweiland.net",
+ "url": "https://www.youtube.com/c/TomWeiland"
+ },
+ "dependencies": {},
+ "keywords": [
+ "riptide",
+ "networking",
+ "multiplayer"
+ ],
+ "repository": {
+ "url": "https://github.com/tom-weiland/RiptideNetworking",
+ "type": "git"
+ }
}
\ No newline at end of file