Skip to content

Commit

Permalink
Adding an automatic reconnection process to RoomClient for recovery a…
Browse files Browse the repository at this point in the history
…fter the connection to Nexus is lost. On connection loss, the room client instructs the scene to drop all connections and periodically attempts at rejoining the room.
  • Loading branch information
FJThiel committed Jul 4, 2023
1 parent fe6b604 commit 85c1f96
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 1 deletion.
20 changes: 20 additions & 0 deletions Unity/Assets/Runtime/Messaging/NetworkScene.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,26 @@ public void AddConnection(INetworkConnection connection)
connections.Add(connection);
}

/// <summary>
/// Public method instructing the network scene to drop all current connections and dispose of them.
/// Used to recover from a connection loss to Nexus.
/// </summary>
public void ResetConnections()
{
foreach (var c in connections)
{
try
{
c.Dispose();
}
catch
{

}
}
connections.Clear();
}

private void Update()
{
OnUpdate.Invoke();
Expand Down
101 changes: 100 additions & 1 deletion Unity/Assets/Runtime/Rooms/RoomClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using Codice.Client.Commands;
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.Linq;
using Ubiq.Dictionaries;
Expand Down Expand Up @@ -240,6 +242,20 @@ public IEnumerable<IPeer> Peers
private float heartbeatSent => Time.realtimeSinceStartup - pingSent;
public static float HeartbeatTimeout = 5f;
public static float HeartbeatInterval = 1f;

// Parameters private and public for the reconnection process.
private enum ReconnectionStatus { Off, Reset, Received, Rejoining};
private ReconnectionStatus reconnectionStatus = ReconnectionStatus.Off;
private float lastResetTime = 0;
private float timeSinceLastReset => Time.realtimeSinceStartup - lastResetTime;
private float lastRejoinTime = 0;
private float timeSinceLastRejoin => Time.realtimeSinceStartup - lastRejoinTime;
private Guid previousRoomGUID = Guid.Empty;
public static bool AttemtReconnecting = true; // Whether the RoomClient attemtps to reconnect to the server on connection loss.
public static float ReconnectTimeout = 5f; // How long a timeout can last until the reconnect procedure is triggered.
public static float ReconnectInterval = 5f; // The intervals in which reconnect attempts are done.
public static float RejoinInterval = 5f; // The intervals in which rejoin attempts are done.

private PeerInterfaceFriend me = new PeerInterfaceFriend(Guid.NewGuid().ToString());
private RoomInterfaceFriend room = new RoomInterfaceFriend();
private NetworkScene scene;
Expand Down Expand Up @@ -547,6 +563,11 @@ protected void ProcessMessage(ReferenceCountedSceneGraphMessage message)
case "Ping":
{
pingReceived = Time.realtimeSinceStartup;

// Set flag for first ping after connection loss
if (reconnectionStatus == ReconnectionStatus.Reset)
reconnectionStatus = ReconnectionStatus.Received;

PlayerNotifications.Delete(ref notification);
var response = JsonUtility.FromJson<PingResponseArgs>(container.args);
OnPingResponse(response);
Expand Down Expand Up @@ -635,6 +656,38 @@ public void Connect(ConnectionDefinition connection)
scene.AddConnection(Connections.Resolve(connection));
}

/// <summary>
/// Method to reset all current connections and reconnect to the ones defined by the user in the Unity UI.
/// </summary>
public void ResetAndReconnect()
{
ResetAndReconnect(servers);
}

/// <summary>
/// Method to reset all current connections and reconnect to the ones defined in the connection definition passed as argument.
/// </summary>
/// <param name="connectionDefinitions">The connection definition that will be connected after the reset.</param>
public void ResetAndReconnect(ConnectionDefinition[] connectionDefinitions)
{
// Drop all connections
scene.ResetConnections();

// Reconnect all connections
foreach (var item in connectionDefinitions)
{
try
{
Connect(item);
}
catch(Exception e)
{
Debug.LogError(e.ToString());
}
}
}


private void Update()
{
actions.ForEach(a => a());
Expand Down Expand Up @@ -679,6 +732,52 @@ private void Update()
notification = PlayerNotifications.Show(new TimeoutNotification(this));
}
}

// Reconnection behaviour
// Test if dynamic reconnection is enabled
if(AttemtReconnecting)
{
// If enabled, check for current reconnection status
switch (reconnectionStatus)
{
// Reconnection off: If reconnect timeout is exceeded, old room GUID is stored, reset initiated, and next state is initiaded.
case ReconnectionStatus.Off:
if(heartbeatReceived > ReconnectTimeout)
{
previousRoomGUID = new Guid(room.UUID);
ResetAndReconnect();
lastResetTime = Time.realtimeSinceStartup;
reconnectionStatus = ReconnectionStatus.Reset;
}
break;
// Reconnection Reset: Connection is reset. While no response has been received, reset connection again in regular intervals.
case ReconnectionStatus.Reset:
if (timeSinceLastReset > ReconnectInterval)
{
ResetAndReconnect();
lastResetTime = Time.realtimeSinceStartup;
}
break;
// Reconnection Received: A response by Nexus has received. Attempt at rejoining room.
case ReconnectionStatus.Received:
Join(previousRoomGUID);
lastRejoinTime = Time.realtimeSinceStartup;
reconnectionStatus = ReconnectionStatus.Rejoining;
break;
// Reconnection Rejoining: Re-attempt rejoining room at regular intervals until succesful.
case ReconnectionStatus.Rejoining:
if(room.UUID == previousRoomGUID.ToString())
{
reconnectionStatus = ReconnectionStatus.Off;
}
else if(timeSinceLastRejoin > RejoinInterval)
{
Join(previousRoomGUID);
lastRejoinTime = Time.realtimeSinceStartup;
}
break;
}
}
}

/// <summary>
Expand Down

0 comments on commit 85c1f96

Please sign in to comment.