Skip to content

Commit

Permalink
feat: adding disconnect reason to client disconnect (#820)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Client.Disconnected now has a Reason argument
  • Loading branch information
James-Frowen committed May 27, 2021
1 parent 89aca7b commit e597570
Show file tree
Hide file tree
Showing 20 changed files with 328 additions and 49 deletions.
2 changes: 1 addition & 1 deletion Assets/Mirage/Components/OnlineOfflineScene.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ void Start()
}
}

void OnClientDisconnected()
void OnClientDisconnected(ClientStoppedReason reason)
{
SceneManager.LoadSceneAsync(OfflineScene);
}
Expand Down
2 changes: 1 addition & 1 deletion Assets/Mirage/Runtime/ClientObjectManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ void OnClientConnected(INetworkPlayer player)
}
}

void OnClientDisconnected()
void OnClientDisconnected(ClientStoppedReason reason)
{
ClearSpawners();
DestroyAllClientObjects();
Expand Down
63 changes: 63 additions & 0 deletions Assets/Mirage/Runtime/ClientStoppedReason.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using Mirage.SocketLayer;

namespace Mirage
{
/// <summary>
/// Reason why Client was stopped or disconnected
/// </summary>
/// <remarks>
/// Use different enums than SocketLayer so that:
/// User doesn't need to add reference to socket layer to use event;
/// Give high level reason so that they are easierto understand by the end user.
/// </remarks>
[Serializable]
public enum ClientStoppedReason
{
/// <summary>No reason given</summary>
None = 0,

/// <summary>Connecting timed out
/// <para>Server not sending replies</para></summary>
Timeout = 1,
/// <summary>Connection disconnect called locally</summary>
LocalConnectionClosed = 2,
/// <summary>Connection disconnect called on server</summary>
RemoteConnectionClosed = 3,

/// <summary>Server rejected connecting because it was full</summary>
ServerFull = 4,
/// <summary>Server did not reply</summary>
ConnectingTimeout = 5,
/// <summary>Disconnect called locally before server replies with connected</summary>
ConnectingCancel = 6,
}


internal static class StoppedReasonExtensions
{
public static ClientStoppedReason ToClientStoppedReason(this DisconnectReason reason)
{
switch (reason)
{
default:
case DisconnectReason.None: return ClientStoppedReason.None;
case DisconnectReason.Timeout: return ClientStoppedReason.Timeout;
case DisconnectReason.RequestedByRemotePeer: return ClientStoppedReason.RemoteConnectionClosed;
case DisconnectReason.RequestedByLocalPeer: return ClientStoppedReason.LocalConnectionClosed;
}
}

public static ClientStoppedReason ToClientStoppedReason(this RejectReason reason)
{
switch (reason)
{
default:
case RejectReason.None: return ClientStoppedReason.None;
case RejectReason.Timeout: return ClientStoppedReason.ConnectingTimeout;
case RejectReason.ServerFull: return ClientStoppedReason.ServerFull;
case RejectReason.ClosedByPeer: return ClientStoppedReason.ConnectingCancel;
}
}
}
}
11 changes: 11 additions & 0 deletions Assets/Mirage/Runtime/ClientStoppedReason.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions Assets/Mirage/Runtime/Events/DisconnectAddLateEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using UnityEngine.Events;

namespace Mirage.Events
{
[Serializable] public class DisconnectEvent : UnityEvent<ClientStoppedReason> { }

/// <summary>
/// Event fires from a <see cref="NetworkClient">NetworkClient</see> when it fails to connect to the server
/// </summary>
[Serializable] public class DisconnectAddLateEvent : AddLateEvent<ClientStoppedReason, DisconnectEvent> { }
}
11 changes: 11 additions & 0 deletions Assets/Mirage/Runtime/Events/DisconnectAddLateEvent.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions Assets/Mirage/Runtime/Events/NetworkPlayerAddLateEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@

namespace Mirage.Events
{
[Serializable] public class NetworkPlayerEvent : UnityEvent<INetworkPlayer> { }

/// <summary>
/// Event fires from a <see cref="NetworkClient">NetworkClient</see> or <see cref="NetworkServer">NetworkServer</see> during a new connection, a new authentication, or a disconnection.
/// <para>INetworkConnection - connection creating the event</para>
/// </summary>
[Serializable] public class NetworkPlayerEvent : UnityEvent<INetworkPlayer> { }

[Serializable]
public class NetworkPlayerAddLateEvent : AddLateEvent<INetworkPlayer, NetworkPlayerEvent> { }
[Serializable] public class NetworkPlayerAddLateEvent : AddLateEvent<INetworkPlayer, NetworkPlayerEvent> { }
}
2 changes: 1 addition & 1 deletion Assets/Mirage/Runtime/INetworkClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public interface INetworkClient : IMessageSender
/// <summary>
/// Event fires after the Client has disconnected from its Server and Cleanup has been called.
/// </summary>
IAddLateEvent Disconnected { get; }
IAddLateEvent<ClientStoppedReason> Disconnected { get; }

/// <summary>
/// The NetworkConnection object this client is using.
Expand Down
20 changes: 7 additions & 13 deletions Assets/Mirage/Runtime/NetworkClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class NetworkClient : MonoBehaviour, INetworkClient
[Header("Events")]
[SerializeField] NetworkPlayerAddLateEvent _connected = new NetworkPlayerAddLateEvent();
[SerializeField] NetworkPlayerAddLateEvent _authenticated = new NetworkPlayerAddLateEvent();
[SerializeField] AddLateEvent _disconnected = new AddLateEvent();
[SerializeField] DisconnectAddLateEvent _disconnected = new DisconnectAddLateEvent();

/// <summary>
/// Event fires once the Client has connected its Server.
Expand All @@ -54,7 +54,7 @@ public class NetworkClient : MonoBehaviour, INetworkClient
/// <summary>
/// Event fires after the Client has disconnected from its Server and Cleanup has been called.
/// </summary>
public IAddLateEvent Disconnected => _disconnected;
public IAddLateEvent<ClientStoppedReason> Disconnected => _disconnected;

/// <summary>
/// The NetworkConnection object this client is using.
Expand Down Expand Up @@ -144,21 +144,16 @@ private void Peer_OnConnected(IConnection conn)
private void Peer_OnConnectionFailed(IConnection conn, RejectReason reason)
{
if (logger.LogEnabled()) logger.Log($"Failed to connect to {conn.EndPoint} with reason {reason}");
Player.MarkAsDisconnected();
// todo add connection failed event
_disconnected?.Invoke();
Player?.MarkAsDisconnected();
_disconnected?.Invoke(reason.ToClientStoppedReason());
Cleanup();
}

private void Peer_OnDisconnected(IConnection conn, DisconnectReason reason)
{
if (logger.LogEnabled()) logger.Log($"Disconnected from {conn.EndPoint} with reason {reason}");
Player?.MarkAsDisconnected();
// todo add reason to disconnected event
// use different enum, so that:
// - user doesn't need to add reference to socket layer
// - add high level reason so that they are easier to understand by user
_disconnected?.Invoke();
_disconnected?.Invoke(reason.ToClientStoppedReason());
Cleanup();
}

Expand Down Expand Up @@ -213,10 +208,9 @@ internal void OnAuthenticated(INetworkPlayer player)
/// </summary>
public void Disconnect()
{
// todo exit early if not active/initialized
if (!Active) return;

Player?.Connection?.Disconnect();
_disconnected?.Invoke();
Player.Connection.Disconnect();
Cleanup();
}

Expand Down
2 changes: 1 addition & 1 deletion Assets/Mirage/Runtime/SocketLayer/Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ void IRawConnection.SendRaw(byte[] packet, int length)
/// </summary>
public void Disconnect()
{
Disconnect(DisconnectReason.RequestedByPeer);
Disconnect(DisconnectReason.RequestedByLocalPeer);
}
internal void Disconnect(DisconnectReason reason, bool sendToOther = true)
{
Expand Down
7 changes: 6 additions & 1 deletion Assets/Mirage/Runtime/SocketLayer/Enums/DisconnectReason.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public enum DisconnectReason
/// <summary>
/// Disconnect called by higher level
/// </summary>
RequestedByPeer = 2,
RequestedByRemotePeer = 2,

/// <summary>
/// Disconnect called by higher level
/// </summary>
RequestedByLocalPeer = 3,
}
}
8 changes: 6 additions & 2 deletions Assets/Mirage/Runtime/SocketLayer/Peer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public void Close()
// send disconnect messages
foreach (Connection conn in connections.Values)
{
conn.Disconnect(DisconnectReason.RequestedByPeer);
conn.Disconnect(DisconnectReason.RequestedByRemotePeer);
}
RemoveConnections();

Expand Down Expand Up @@ -479,7 +479,11 @@ internal void OnConnectionDisconnected(Connection connection, DisconnectReason r
{
if (sendToOther)
{
SendCommand(connection, Commands.Disconnect, (byte)reason);
// if reason is ByLocal, then change it to ByRemote for sending
byte byteReason = (byte)(reason == DisconnectReason.RequestedByLocalPeer
? DisconnectReason.RequestedByRemotePeer
: reason);
SendCommand(connection, Commands.Disconnect, byteReason);
}

// tell high level
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public void OnStopServer()
StartCoroutine(UnloadSubScenes());
}

public void OnStopClient()
public void OnStopClient(ClientStoppedReason reason)
{
if (!Server.Active)
StartCoroutine(UnloadClientSubScenes());
Expand Down
12 changes: 11 additions & 1 deletion Assets/Tests/Common/TestSocketFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ public class TestSocket : ISocket
/// <summary>
/// this static dictionary will act as the internet
/// </summary>
static Dictionary<EndPoint, TestSocket> allSockets = new Dictionary<EndPoint, TestSocket>();
public static Dictionary<EndPoint, TestSocket> allSockets = new Dictionary<EndPoint, TestSocket>();

/// <summary>
/// Can be useful to fake timeouts or dropped messages
/// </summary>
public static bool StopAllMessages;

public static bool EndpointInUse(EndPoint endPoint) => allSockets.ContainsKey(endPoint);

Expand Down Expand Up @@ -97,6 +102,11 @@ void ISocket.Send(EndPoint remoteEndPoint, byte[] data, int length)
data = clone,
length = length
});

// mark as sent, but not as received
if (StopAllMessages)
return;

other.received.Enqueue(new Packet
{
endPoint = endPoint,
Expand Down
40 changes: 21 additions & 19 deletions Assets/Tests/Runtime/ClientServer/ClientServerSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ namespace Mirage.Tests.Runtime.ClientServer
public class ClientServerSetup<T> where T : NetworkBehaviour
{

#region Setup
protected GameObject serverGo;
protected NetworkServer server;
protected NetworkSceneManager serverSceneManager;
Expand All @@ -38,6 +37,8 @@ public class ClientServerSetup<T> where T : NetworkBehaviour

public virtual void ExtraSetup() { }

protected virtual bool AutoConnectClient => true;

[UnitySetUp]
public IEnumerator Setup() => UniTask.ToCoroutine(async () =>
{
Expand Down Expand Up @@ -88,27 +89,30 @@ public class ClientServerSetup<T> where T : NetworkBehaviour
await started.Task;
// now start the client
client.Connect("localhost");
if (AutoConnectClient)
{
// now start the client
client.Connect("localhost");
await AsyncUtil.WaitUntilWithTimeout(() => server.Players.Count > 0);
await AsyncUtil.WaitUntilWithTimeout(() => server.Players.Count > 0);
// get the connections so that we can spawn players
connectionToClient = server.Players.First();
connectionToServer = client.Player;
// get the connections so that we can spawn players
connectionToClient = server.Players.First();
connectionToServer = client.Player;
// create a player object in the server
serverPlayerGO = Object.Instantiate(playerPrefab);
serverIdentity = serverPlayerGO.GetComponent<NetworkIdentity>();
serverComponent = serverPlayerGO.GetComponent<T>();
serverObjectManager.AddCharacter(connectionToClient, serverPlayerGO);
// create a player object in the server
serverPlayerGO = Object.Instantiate(playerPrefab);
serverIdentity = serverPlayerGO.GetComponent<NetworkIdentity>();
serverComponent = serverPlayerGO.GetComponent<T>();
serverObjectManager.AddCharacter(connectionToClient, serverPlayerGO);
// wait for client to spawn it
await AsyncUtil.WaitUntilWithTimeout(() => connectionToServer.Identity != null);
// wait for client to spawn it
await AsyncUtil.WaitUntilWithTimeout(() => connectionToServer.Identity != null);
clientIdentity = connectionToServer.Identity;
clientPlayerGO = clientIdentity.gameObject;
clientComponent = clientPlayerGO.GetComponent<T>();
clientIdentity = connectionToServer.Identity;
clientPlayerGO = clientIdentity.gameObject;
clientComponent = clientPlayerGO.GetComponent<T>();
}
});

public virtual void ExtraTearDown() { }
Expand All @@ -130,7 +134,5 @@ public class ClientServerSetup<T> where T : NetworkBehaviour
ExtraTearDown();
});

#endregion
}
}

0 comments on commit e597570

Please sign in to comment.