diff --git a/UnityPackage/Runtime/Client.cs b/UnityPackage/Runtime/Client.cs
index a9e01cfc..fa8c3cd2 100644
--- a/UnityPackage/Runtime/Client.cs
+++ b/UnityPackage/Runtime/Client.cs
@@ -69,7 +69,7 @@ public void ChangeTransport(IClient client)
this.client = client;
}
- /// Attempts connect to the given host address.
+ /// Attempts to 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 .
///
@@ -82,12 +82,12 @@ public void Connect(string hostAddress, byte messageHandlerGroupId = 0)
CreateMessageHandlersDictionary(Assembly.GetCallingAssembly(), messageHandlerGroupId);
- client.Connected += Connected;
+ client.Connected += OnConnected;
client.ConnectionFailed += OnConnectionFailed;
client.MessageReceived += OnMessageReceived;
client.Disconnected += OnDisconnected;
- client.ClientConnected += ClientConnected;
- client.ClientDisconnected += ClientDisconnected;
+ client.ClientConnected += OnClientConnected;
+ client.ClientDisconnected += OnClientDisconnected;
client.Connect(hostAddress);
}
@@ -95,7 +95,7 @@ public void Connect(string hostAddress, byte messageHandlerGroupId = 0)
protected override void CreateMessageHandlersDictionary(Assembly assembly, byte messageHandlerGroupId)
{
MethodInfo[] methods = assembly.GetTypes()
- .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static))
+ .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)) // Include instance methods in the search so we can show the developer an error instead of silently not adding instance methods to the dictionary
.Where(m => m.GetCustomAttributes(typeof(MessageHandlerAttribute), false).Length > 0)
.ToArray();
@@ -107,17 +107,17 @@ protected override void CreateMessageHandlersDictionary(Assembly assembly, byte
break;
if (!methods[i].IsStatic)
- {
- RiptideLogger.Log(LogType.error, $"Message handler methods should be static, but '{methods[i].DeclaringType}.{methods[i].Name}' is an instance method!");
- break;
- }
+ throw new Exception($"Message handler methods should be static, but '{methods[i].DeclaringType}.{methods[i].Name}' is an instance method!");
Delegate clientMessageHandler = Delegate.CreateDelegate(typeof(MessageHandler), methods[i], false);
if (clientMessageHandler != null)
{
// It's a message handler for Client instances
if (messageHandlers.ContainsKey(attribute.MessageId))
- RiptideLogger.Log(LogType.error, $"Message handler method (type: client) already exists for message ID {attribute.MessageId}! Only one handler method is allowed per ID!");
+ {
+ MethodInfo otherMethodWithId = messageHandlers[attribute.MessageId].GetMethodInfo();
+ throw new Exception($"Client-side message handler methods '{methods[i].DeclaringType}.{methods[i].Name}' and '{otherMethodWithId.DeclaringType}.{otherMethodWithId.Name}' are both set to handle messages with ID {attribute.MessageId}! Only one handler method is allowed per message ID!");
+ }
else
messageHandlers.Add(attribute.MessageId, (MessageHandler)clientMessageHandler);
}
@@ -126,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(LogType.error, $"'{methods[i].DeclaringType}.{methods[i].Name}' doesn't match any acceptable message handler method signatures, double-check its parameters!");
+ throw new Exception($"'{methods[i].DeclaringType}.{methods[i].Name}' doesn't match any acceptable message handler method signatures, double-check its parameters!");
}
}
}
@@ -149,14 +149,17 @@ public void Disconnect()
private void LocalDisconnect()
{
- client.Connected -= Connected;
- client.ConnectionFailed -= ConnectionFailed;
+ client.Connected -= OnConnected;
+ client.ConnectionFailed -= OnConnectionFailed;
client.MessageReceived -= OnMessageReceived;
- client.Disconnected -= Disconnected;
- client.ClientConnected -= ClientConnected;
- client.ClientDisconnected -= ClientDisconnected;
+ client.Disconnected -= OnDisconnected;
+ client.ClientConnected -= OnClientConnected;
+ client.ClientDisconnected -= OnClientDisconnected;
}
+ /// Invokes the event.
+ private void OnConnected(object s, EventArgs e) => Connected?.Invoke(this, e);
+
/// Invokes the event.
private void OnConnectionFailed(object s, EventArgs e)
{
@@ -172,7 +175,7 @@ private void OnMessageReceived(object s, ClientMessageReceivedEventArgs e)
if (messageHandlers.TryGetValue(e.MessageId, out MessageHandler messageHandler))
messageHandler(e.Message);
else
- RiptideLogger.Log(LogType.warning, $"No handler method (type: client) found for message ID {e.MessageId}!");
+ RiptideLogger.Log(LogType.warning, $"No client-side handler method found for message ID {e.MessageId}!");
}
/// Invokes the event.
@@ -181,5 +184,11 @@ private void OnDisconnected(object s, EventArgs e)
LocalDisconnect();
Disconnected?.Invoke(this, e);
}
+
+ /// Invokes the event.
+ private void OnClientConnected(object s, ClientConnectedEventArgs e) => ClientConnected?.Invoke(this, e);
+
+ /// Invokes the event.
+ private void OnClientDisconnected(object s, ClientDisconnectedEventArgs e) => ClientDisconnected?.Invoke(this, e);
}
}
diff --git a/UnityPackage/Runtime/Message.cs b/UnityPackage/Runtime/Message.cs
index 70a6cd6c..ea88a59e 100644
--- a/UnityPackage/Runtime/Message.cs
+++ b/UnityPackage/Runtime/Message.cs
@@ -202,6 +202,10 @@ public HeaderType PrepareForUse(ushort contentLength)
#region Add & Retrieve Data
#region Byte
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a to the message.
+ public Message AddByte(byte value) => Add(value);
+
/// Adds a single to the message.
/// The to add.
/// The message that the was added to.
@@ -227,6 +231,10 @@ public byte GetByte()
return Bytes[readPos++]; // Get the byte at readPos' position
}
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a array to the message.
+ public Message AddBytes(byte[] value, bool includeLength = true, bool isBigArray = false) => Add(value, includeLength, isBigArray);
+
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
@@ -315,6 +323,10 @@ private void ReadBytes(int amount, byte[] array, int startIndex = 0)
#endregion
#region Bool
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a to the message.
+ public Message AddBool(bool value) => Add(value);
+
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
@@ -340,6 +352,10 @@ public bool GetBool()
return Bytes[readPos++] == 1; // Convert the byte at readPos' position to a bool
}
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a array to the message.
+ public Message AddBools(bool[] value, bool includeLength = true, bool isBigArray = false) => Add(value, includeLength, isBigArray);
+
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
@@ -460,6 +476,10 @@ private void ReadBools(int byteAmount, bool[] array, int startIndex = 0)
#endregion
#region Short & UShort
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a to the message.
+ public Message AddShort(short value) => Add(value);
+
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
@@ -473,6 +493,10 @@ public Message Add(short value)
return this;
}
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a to the message.
+ public Message AddUShort(ushort value) => Add(value);
+
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
@@ -528,6 +552,10 @@ internal ushort PeekUShort()
return RiptideConverter.ToUShort(Bytes, readPos);
}
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a array to the message.
+ public Message AddShorts(short[] value, bool includeLength = true, bool isBigArray = false) => Add(value, includeLength, isBigArray);
+
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
@@ -562,6 +590,10 @@ public Message Add(short[] array, bool includeLength = true, bool isBigArray = f
return this;
}
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a array to the message.
+ public Message AddUShorts(ushort[] value, bool includeLength = true, bool isBigArray = false) => Add(value, includeLength, isBigArray);
+
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
@@ -710,6 +742,10 @@ private void ReadUShorts(int amount, ushort[] array, int startIndex = 0)
#endregion
#region Int & UInt
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a to the message.
+ public Message AddInt(int value) => Add(value);
+
/// Adds an to the message.
/// The to add.
/// The message that the was added to.
@@ -723,6 +759,10 @@ public Message Add(int value)
return this;
}
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a to the message.
+ public Message AddUInt(uint value) => Add(value);
+
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
@@ -766,6 +806,10 @@ public uint GetUInt()
return value;
}
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a array to the message.
+ public Message AddInts(int[] value, bool includeLength = true, bool isBigArray = false) => Add(value, includeLength, isBigArray);
+
/// Adds an array message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
@@ -800,6 +844,10 @@ public Message Add(int[] array, bool includeLength = true, bool isBigArray = fal
return this;
}
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a array to the message.
+ public Message AddUInts(uint[] value, bool includeLength = true, bool isBigArray = false) => Add(value, includeLength, isBigArray);
+
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
@@ -948,6 +996,10 @@ private void ReadUInts(int amount, uint[] array, int startIndex = 0)
#endregion
#region Long & ULong
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a to the message.
+ public Message AddLong(long value) => Add(value);
+
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
@@ -961,6 +1013,10 @@ public Message Add(long value)
return this;
}
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a to the message.
+ public Message AddULong(ulong value) => Add(value);
+
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
@@ -1004,6 +1060,10 @@ public ulong GetULong()
return value;
}
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a array to the message.
+ public Message AddLongs(long[] value, bool includeLength = true, bool isBigArray = false) => Add(value, includeLength, isBigArray);
+
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
@@ -1038,6 +1098,10 @@ public Message Add(long[] array, bool includeLength = true, bool isBigArray = fa
return this;
}
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a array to the message.
+ public Message AddULongs(ulong[] value, bool includeLength = true, bool isBigArray = false) => Add(value, includeLength, isBigArray);
+
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
@@ -1186,6 +1250,10 @@ private void ReadULongs(int amount, ulong[] array, int startIndex = 0)
#endregion
#region Float
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a to the message.
+ public Message AddFloat(float value) => Add(value);
+
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
@@ -1214,6 +1282,10 @@ public float GetFloat()
return value;
}
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a array to the message.
+ public Message AddFloats(float[] value, bool includeLength = true, bool isBigArray = false) => Add(value, includeLength, isBigArray);
+
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
@@ -1306,6 +1378,10 @@ private void ReadFloats(int amount, float[] array, int startIndex = 0)
#endregion
#region Double
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a to the message.
+ public Message AddDouble(double value) => Add(value);
+
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
@@ -1334,6 +1410,10 @@ public double GetDouble()
return value;
}
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a array to the message.
+ public Message AddDoubles(double[] value, bool includeLength = true, bool isBigArray = false) => Add(value, includeLength, isBigArray);
+
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
@@ -1426,6 +1506,10 @@ private void ReadDoubles(int amount, double[] array, int startIndex = 0)
#endregion
#region String
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a to the message.
+ public Message AddString(string value) => Add(value);
+
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
@@ -1457,6 +1541,10 @@ public string GetString()
return value;
}
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a array to the message.
+ public Message AddStrings(string[] value, bool includeLength = true, bool isBigArray = false) => Add(value, includeLength, isBigArray);
+
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
diff --git a/UnityPackage/Runtime/MessageExtensionsUnity.cs b/UnityPackage/Runtime/MessageExtensionsUnity.cs
index 068f4d2f..7f5ed2f6 100644
--- a/UnityPackage/Runtime/MessageExtensionsUnity.cs
+++ b/UnityPackage/Runtime/MessageExtensionsUnity.cs
@@ -10,13 +10,17 @@ namespace RiptideNetworking
public static class MessageExtensionsUnity
{
#region Vector2
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a to the message.
+ public static Message AddVector2(this Message message, Vector2 value) => Add(message, value);
+
/// 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);
+ message.AddFloat(value.x);
+ message.AddFloat(value.y);
return message;
}
@@ -29,14 +33,18 @@ public static Vector2 GetVector2(this Message message)
#endregion
#region Vector3
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a to the message.
+ public static Message AddVector3(this Message message, Vector3 value) => Add(message, value);
+
/// 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);
+ message.AddFloat(value.x);
+ message.AddFloat(value.y);
+ message.AddFloat(value.z);
return message;
}
@@ -49,15 +57,19 @@ public static Vector3 GetVector3(this Message message)
#endregion
#region Quaternion
+ ///
+ /// Relying on the correct Add overload being chosen based on the parameter type can increase the odds of accidental type mismatches when retrieving data from a message. This method calls and simply provides an alternative type-explicit way to add a to the message.
+ public static Message AddQuaternion(this Message message, Quaternion value) => Add(message, value);
+
/// 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);
+ message.AddFloat(value.x);
+ message.AddFloat(value.y);
+ message.AddFloat(value.z);
+ message.AddFloat(value.w);
return message;
}
diff --git a/UnityPackage/Runtime/Server.cs b/UnityPackage/Runtime/Server.cs
index 75048ad6..8538ff2f 100644
--- a/UnityPackage/Runtime/Server.cs
+++ b/UnityPackage/Runtime/Server.cs
@@ -71,19 +71,19 @@ public void Start(ushort port, ushort maxClientCount, byte messageHandlerGroupId
CreateMessageHandlersDictionary(Assembly.GetCallingAssembly(), messageHandlerGroupId);
- server.ClientConnected += ClientConnected;
+ server.ClientConnected += OnClientConnected;
server.MessageReceived += OnMessageReceived;
- server.ClientDisconnected += ClientDisconnected;
+ server.ClientDisconnected += OnClientDisonnected;
server.Start(port, maxClientCount);
IsRunning = true;
}
-
+
///
protected override void CreateMessageHandlersDictionary(Assembly assembly, byte messageHandlerGroupId)
{
MethodInfo[] methods = assembly.GetTypes()
- .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static))
+ .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)) // Include instance methods in the search so we can show the developer an error instead of silently not adding instance methods to the dictionary
.Where(m => m.GetCustomAttributes(typeof(MessageHandlerAttribute), false).Length > 0)
.ToArray();
@@ -95,17 +95,17 @@ protected override void CreateMessageHandlersDictionary(Assembly assembly, byte
break;
if (!methods[i].IsStatic)
- {
- RiptideLogger.Log(LogType.error, $"Message handler methods should be static, but '{methods[i].DeclaringType}.{methods[i].Name}' is an instance method!");
- break;
- }
+ throw new Exception($"Message handler methods should be static, but '{methods[i].DeclaringType}.{methods[i].Name}' is an instance method!");
Delegate clientMessageHandler = Delegate.CreateDelegate(typeof(MessageHandler), methods[i], false);
if (clientMessageHandler != null)
{
// It's a message handler for Server instances
if (messageHandlers.ContainsKey(attribute.MessageId))
- RiptideLogger.Log(LogType.error, $"Message handler method (type: server) already exists for message ID {attribute.MessageId}! Only one handler method is allowed per ID!");
+ {
+ MethodInfo otherMethodWithId = messageHandlers[attribute.MessageId].GetMethodInfo();
+ throw new Exception($"Server-side message handler methods '{methods[i].DeclaringType}.{methods[i].Name}' and '{otherMethodWithId.DeclaringType}.{otherMethodWithId.Name}' are both set to handle messages with ID {attribute.MessageId}! Only one handler method is allowed per message ID!");
+ }
else
messageHandlers.Add(attribute.MessageId, (MessageHandler)clientMessageHandler);
}
@@ -114,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(LogType.error, $"'{methods[i].DeclaringType}.{methods[i].Name}' doesn't match any acceptable message handler method signatures, double-check its parameters!");
+ throw new Exception($"'{methods[i].DeclaringType}.{methods[i].Name}' doesn't match any acceptable message handler method signatures, double-check its parameters!");
}
}
}
@@ -141,13 +141,16 @@ public void Stop()
return;
server.Shutdown();
- server.ClientConnected -= ClientConnected;
+ server.ClientConnected -= OnClientConnected;
server.MessageReceived -= OnMessageReceived;
- server.ClientDisconnected -= ClientDisconnected;
+ server.ClientDisconnected -= OnClientDisonnected;
IsRunning = false;
}
+ /// Invokes the event.
+ private void OnClientConnected(object sender, ServerClientConnectedEventArgs e) => ClientConnected?.Invoke(this, e);
+
/// Invokes the event and initiates handling of the received message.
private void OnMessageReceived(object s, ServerMessageReceivedEventArgs e)
{
@@ -156,7 +159,10 @@ private void OnMessageReceived(object s, ServerMessageReceivedEventArgs e)
if (messageHandlers.TryGetValue(e.MessageId, out MessageHandler messageHandler))
messageHandler(e.FromClientId, e.Message);
else
- RiptideLogger.Log(LogType.warning, $"No handler method (type: server) found for message ID {e.MessageId}!");
+ RiptideLogger.Log(LogType.warning, $"No server-side handler method found for message ID {e.MessageId}!");
}
+
+ /// Invokes the event.
+ private void OnClientDisonnected(object sender, ClientDisconnectedEventArgs e) => ClientDisconnected?.Invoke(this, e);
}
}
diff --git a/UnityPackage/Runtime/Transports/IClient.cs b/UnityPackage/Runtime/Transports/IClient.cs
index c0ebe713..bfd6949d 100644
--- a/UnityPackage/Runtime/Transports/IClient.cs
+++ b/UnityPackage/Runtime/Transports/IClient.cs
@@ -24,7 +24,7 @@ public interface IClient : ICommon, IConnectionInfo
/// Invoked when a client disconnects.
event EventHandler ClientDisconnected;
- /// Attempts connect to the given host address.
+ /// Attempts to connect to the given host address.
/// The host address to connect to.
void Connect(string hostAddress);
/// Sends a message to the server.
diff --git a/UnityPackage/Runtime/Transports/ICommon.cs b/UnityPackage/Runtime/Transports/ICommon.cs
index 4d8e15de..3a94e744 100644
--- a/UnityPackage/Runtime/Transports/ICommon.cs
+++ b/UnityPackage/Runtime/Transports/ICommon.cs
@@ -3,10 +3,6 @@
// 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;
-using System.Text;
-
namespace RiptideNetworking.Transports
{
/// Defines methods, properties, and events which every transport's server and client must implement.
diff --git a/UnityPackage/Runtime/Transports/RudpTransport/RudpListener.cs b/UnityPackage/Runtime/Transports/RudpTransport/RudpListener.cs
index e4225522..70efa815 100644
--- a/UnityPackage/Runtime/Transports/RudpTransport/RudpListener.cs
+++ b/UnityPackage/Runtime/Transports/RudpTransport/RudpListener.cs
@@ -17,7 +17,7 @@ public abstract class RudpListener
/// The name to use when logging messages via .
public readonly string LogName;
- /// The to use when invoking events. if events should be invoked immediately.
+ /// The to use when invoking events.
protected ActionQueue receiveActionQueue;
/// How long to wait for a response, in microseconds.
diff --git a/UnityPackage/Runtime/Utils/LanDiscovery.cs b/UnityPackage/Runtime/Utils/LanDiscovery.cs
new file mode 100644
index 00000000..12eb2f8b
--- /dev/null
+++ b/UnityPackage/Runtime/Utils/LanDiscovery.cs
@@ -0,0 +1,314 @@
+
+// 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.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+
+namespace RiptideNetworking.Utils
+{
+ /// Defines broadcast modes.
+ public enum BroadcastMode
+ {
+ /// Not currently broadcasting or listening for broadcasts.
+ none,
+ /// Currently broadcasting.
+ broadcasting,
+ /// Currently listening for broadcasts.
+ listening
+ }
+
+ /// Provides functionality for discovering game hosts over LAN.
+ public class LanDiscovery
+ {
+ /// Invoked when a host is found on the LAN.
+ public event EventHandler HostDiscovered;
+
+ /// This app's unique key, used to determine whether to handle or ignore received data.
+ public long UniqueKey { get; set; }
+ /// The current broadcast mode.
+ public BroadcastMode Mode { get; set; }
+ /// The port to send broadcasts to/listen for broadcasts on.
+ public ushort BroadcastPort
+ {
+ get => _broadcastPort;
+ set
+ {
+ if (value == 0)
+ throw new ArgumentOutOfRangeException("Broadcast port cannot be set to 0!");
+
+ if (value != _broadcastPort)
+ {
+ _broadcastPort = value;
+
+ endPoint = new IPEndPoint(IPAddress.Any, value);
+ try
+ {
+ socket.Bind(endPoint);
+ }
+ catch (SocketException ex)
+ {
+ RiptideLogger.Log(LogType.error, $"Failed to bind broadcast socket! Make sure port {_broadcastPort} isn't being used by another {nameof(LanDiscovery)} insance or by another application on this machine, or use a different port for broadcasting. Error: {ex}");
+ }
+ broadcastEndPoint.Port = value;
+ }
+ }
+ }
+ private ushort _broadcastPort;
+ /// The IP to broadcast.
+ public IPAddress HostIP
+ {
+ set => _hostIP = value;
+ protected get => _hostIP;
+ }
+ private IPAddress _hostIP;
+ /// The port to broadcast.
+ public ushort HostPort
+ {
+ set => _hostPort = value;
+ protected get => _hostPort;
+ }
+ private ushort _hostPort;
+
+ /// The current machine's local IP.
+ protected IPAddress localIPAdress;
+ /// The subnet mask for .
+ protected IPAddress subnetMask;
+ /// The endpoint to which to send data in order to broadcast it to all machines on the LAN.
+ protected IPEndPoint broadcastEndPoint;
+ /// The socket used to send and listen for broadcasted data.
+ protected Socket socket;
+ /// A reusable instance.
+ protected EndPoint endPoint;
+ /// The array used to broadcast data.
+ protected byte[] broadcastSendBytes;
+ /// The array into which broadcasted data is received.
+ protected byte[] broadcastReceiveBytes;
+ /// The to use when invoking events.
+ protected ActionQueue actionQueue;
+
+ /// Handles initial setup.
+ /// This app's unique key, used to determine whether to handle or ignore received data.
+ /// The port to send broadcasts to/listen for broadcasts on.
+ public LanDiscovery(long uniqueKey, ushort broadcastPort)
+ {
+ actionQueue = new ActionQueue();
+ UniqueKey = uniqueKey;
+ socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+
+ localIPAdress = GetLocalIPAddress();
+ subnetMask = GetBroadcastAddress(localIPAdress, GetSubnetMask(localIPAdress));
+ broadcastEndPoint = new IPEndPoint(subnetMask, broadcastPort);
+
+ BroadcastPort = broadcastPort;
+ broadcastSendBytes = new byte[14];
+ broadcastReceiveBytes = new byte[14];
+ }
+
+ /// Sends a broadcast to all machines on the LAN.
+ public void SendBroadcast()
+ {
+ if (Mode == BroadcastMode.listening)
+ {
+ RiptideLogger.Log(LogType.error, "LAN Discovery is in listen mode. Change the mode before broadcasting!");
+ return;
+ }
+
+ if (Mode == BroadcastMode.none)
+ socket.BeginReceiveFrom(broadcastReceiveBytes, 0, broadcastReceiveBytes.Length, SocketFlags.None, ref endPoint, ReceiveCallback, null);
+
+ Mode = BroadcastMode.broadcasting;
+
+ SetBroadcastData();
+ socket.SendTo(broadcastSendBytes, broadcastEndPoint);
+ }
+
+ /// Sends a response to a broadcast.
+ /// The endpoint to send the response to.
+ protected void SendBroadcastResponse(IPEndPoint toEndPoint)
+ {
+ SetBroadcastResponseData();
+ socket.SendTo(broadcastSendBytes, toEndPoint);
+ }
+
+ /// Begins listening for broadcasted data.
+ public void StartListening()
+ {
+ if (Mode == BroadcastMode.broadcasting)
+ {
+ RiptideLogger.Log(LogType.error, "LAN Discovery is in broadcast mode. Change the mode before listening!");
+ return;
+ }
+
+ Mode = BroadcastMode.listening;
+ socket.BeginReceiveFrom(broadcastReceiveBytes, 0, broadcastReceiveBytes.Length, SocketFlags.None, ref endPoint, ReceiveCallback, null);
+ }
+
+ /// Receives data.
+ private void ReceiveCallback(IAsyncResult result)
+ {
+ int bytesReceived = socket.EndReceiveFrom(result, ref endPoint);
+ if (bytesReceived < 1)
+ return; // If there's no data
+ else if (((IPEndPoint)endPoint).Address.Equals(localIPAdress))
+ {
+ // If the packet came from this machine we don't want to handle it
+ socket.BeginReceiveFrom(broadcastReceiveBytes, 0, broadcastReceiveBytes.Length, SocketFlags.None, ref endPoint, ReceiveCallback, null);
+ return;
+ }
+
+ if (Mode == BroadcastMode.broadcasting)
+ HandleBroadcastResponseData(bytesReceived);
+ else if (Mode == BroadcastMode.listening)
+ HandleBroadcastData(bytesReceived);
+
+ socket.BeginReceiveFrom(broadcastReceiveBytes, 0, broadcastReceiveBytes.Length, SocketFlags.None, ref endPoint, ReceiveCallback, null);
+ }
+
+ /// Initiates execution of any queued event invocations.
+ /// This should generally be called from within a regularly executed update loop (like FixedUpdate in Unity). Broadcasts will continue to discover hosts on the LAN in between calls, but the event won't be invoked until this method is executed.
+ public virtual void Tick()
+ {
+ if (Mode == BroadcastMode.broadcasting)
+ actionQueue.ExecuteAll();
+ }
+
+ /// Sets the data that will be sent as part of a broadcast.
+ protected virtual void SetBroadcastData()
+ {
+ RiptideConverter.FromLong(UniqueKey, broadcastSendBytes, 0);
+ }
+
+ /// Sets the data that will be sent in response to a broadcast.
+ protected virtual void SetBroadcastResponseData()
+ {
+ int ipBytes = RiptideConverter.ToInt(HostIP.GetAddressBytes(), 0);
+
+ RiptideConverter.FromLong(UniqueKey, broadcastSendBytes, 0);
+ RiptideConverter.FromInt(ipBytes, broadcastSendBytes, 8);
+ RiptideConverter.FromUShort(HostPort, broadcastSendBytes, 12);
+ }
+
+ /// Handles the data received as part of a broadcast.
+ /// The number of bytes that were received.
+ protected virtual void HandleBroadcastData(int bytesReceived)
+ {
+ if (bytesReceived < 8)
+ return; // Not enough bytes to read the expected data, presumably not a broadcast packet from our program
+
+ long key = RiptideConverter.ToLong(broadcastReceiveBytes, 0);
+ if (key != UniqueKey)
+ return; // Key doesn't match, broadcast packet is not from our program
+
+ SendBroadcastResponse((IPEndPoint)endPoint);
+ }
+
+ /// Handles the data received in response to a broadcast.
+ /// The number of bytes that were received.
+ protected virtual void HandleBroadcastResponseData(int bytesReceived)
+ {
+ if (bytesReceived < 14)
+ return; // Not enough bytes to read the data, presumably not a response to a broadcast packet from our program
+
+ long key = RiptideConverter.ToLong(broadcastReceiveBytes, 0);
+ if (key != UniqueKey)
+ return; // Key doesn't match, broadcast response packet is not from our program
+
+ byte[] hostIPBytes = new byte[4];
+ Array.Copy(broadcastReceiveBytes, 8, hostIPBytes, 0, 4);
+ ushort hostPort = RiptideConverter.ToUShort(broadcastReceiveBytes, 12);
+
+ OnHostDiscovered(new IPAddress(hostIPBytes), hostPort);
+ }
+
+ /// Stops all broadcast activities and prepares this instance for reuse.
+ public void Restart()
+ {
+ Stop();
+
+ socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ endPoint = new IPEndPoint(IPAddress.Any, BroadcastPort);
+ socket.Bind(endPoint);
+ }
+
+ /// Stops all broadcast activities.
+ public void Stop()
+ {
+ if (Mode == BroadcastMode.none)
+ return;
+
+ socket.Close();
+ Mode = BroadcastMode.none;
+ }
+
+ /// Retrieves the current machine's local IP.
+ /// The current machine's local IP.
+ public IPAddress GetLocalIPAddress()
+ {
+ IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
+ foreach (IPAddress ip in host.AddressList)
+ if (ip.AddressFamily == AddressFamily.InterNetwork)
+ return ip;
+
+ throw new Exception("No network adapters with an IPv4 address in the system!");
+ }
+
+ /// Calculates the broadcast address, given an IP and its subnet mask.
+ /// The IP to use.
+ /// The subnet mask to use.
+ /// The calculated broadcast address.
+ protected IPAddress GetBroadcastAddress(IPAddress address, IPAddress subnetMask)
+ {
+ // From https://www.medo64.com/2014/12/determining-ipv4-broadcast-address-in-c/
+ int addressInt = RiptideConverter.ToInt(address.GetAddressBytes(), 0);
+ int maskInt = RiptideConverter.ToInt(subnetMask.GetAddressBytes(), 0);
+ int broadcastInt = addressInt | ~maskInt;
+
+ byte[] broadcastIPBytes = new byte[4];
+ RiptideConverter.FromInt(broadcastInt, broadcastIPBytes, 0);
+ return new IPAddress(broadcastIPBytes);
+ }
+
+ /// Takes an IP and retrieves its subnet mask.
+ /// The IP for which to retrieve the subnet mask.
+ /// The retrieved subnet mask.
+ protected IPAddress GetSubnetMask(IPAddress address)
+ {
+ foreach (NetworkInterface adapter in NetworkInterface.GetAllNetworkInterfaces())
+ foreach (UnicastIPAddressInformation unicastIPAddressInformation in adapter.GetIPProperties().UnicastAddresses)
+ if (unicastIPAddressInformation.Address.AddressFamily == AddressFamily.InterNetwork && address.Equals(unicastIPAddressInformation.Address))
+ return unicastIPAddressInformation.IPv4Mask;
+
+ throw new ArgumentException($"Can't find subnet mask for IP address '{address}'!");
+ }
+
+ /// Invokes the .
+ /// The IP of the discovered host.
+ /// The port of the discovered host.
+ protected void OnHostDiscovered(IPAddress ip, ushort port)
+ {
+ actionQueue.Add(() => HostDiscovered?.Invoke(this, new HostDiscoveredEventArgs(ip, port)));
+ }
+ }
+
+ /// Contains event data for when a host is discovered on the LAN.
+ public class HostDiscoveredEventArgs : EventArgs
+ {
+ /// The IP of the discovered host.
+ public IPAddress HostIP { get; private set; }
+ /// The port of the discovered host.
+ public ushort HostPort { get; private set; }
+
+ /// Initializes event data.
+ /// The IP of the discovered host.
+ /// The port of the discovered host.
+ public HostDiscoveredEventArgs(IPAddress hostIP, ushort hostPort)
+ {
+ HostIP = hostIP;
+ HostPort = hostPort;
+ }
+ }
+}
diff --git a/UnityPackage/Runtime/Utils/LanDiscovery.cs.meta b/UnityPackage/Runtime/Utils/LanDiscovery.cs.meta
new file mode 100644
index 00000000..e55e9957
--- /dev/null
+++ b/UnityPackage/Runtime/Utils/LanDiscovery.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d6946ed1995f8c54aa7ad394c77c2765
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/UnityPackage/package.json b/UnityPackage/package.json
index 31173f9c..458dec41 100644
--- a/UnityPackage/package.json
+++ b/UnityPackage/package.json
@@ -1,6 +1,6 @@
{
"name": "net.tomweiland.riptidenetworking",
- "version": "1.0.0",
+ "version": "1.1.0",
"displayName": "RiptideNetworking",
"description": "RiptideNetworking is a lightweight C# networking library primarily designed for use in multiplayer games.",
"unity": "2019.4",