Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Unity.Netcode
internal interface ISceneManagerHandler
{
// Generic action to call when a scene is finished loading/unloading
struct SceneEventAction
class SceneEventAction
Comment thread
NoelStephensUnity marked this conversation as resolved.
Outdated
Comment thread
NoelStephensUnity marked this conversation as resolved.
Outdated
{
internal uint SceneEventId;
internal Action<uint> EventAction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -887,12 +887,14 @@ private SceneEventProgress ValidateSceneEvent(string sceneName, bool isUnloading
private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress)
{
var sceneEventData = BeginSceneEvent();
var clientsThatCompleted = sceneEventProgress.GetClientsWithStatus(true);
var clientsThatTimedOut = sceneEventProgress.GetClientsWithStatus(false);
sceneEventData.SceneEventProgressId = sceneEventProgress.Guid;
sceneEventData.SceneHash = sceneEventProgress.SceneHash;
sceneEventData.SceneEventType = sceneEventProgress.SceneEventType;
sceneEventData.ClientsCompleted = sceneEventProgress.DoneClients;
sceneEventData.ClientsCompleted = clientsThatCompleted;
sceneEventData.LoadSceneMode = sceneEventProgress.LoadSceneMode;
sceneEventData.ClientsTimedOut = sceneEventProgress.ClientsThatStartedSceneEvent.Except(sceneEventProgress.DoneClients).ToList();
sceneEventData.ClientsTimedOut = clientsThatTimedOut;

var message = new SceneEventMessage
{
Expand All @@ -913,8 +915,8 @@ private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress
SceneName = SceneNameFromHash(sceneEventProgress.SceneHash),
ClientId = NetworkManager.ServerClientId,
LoadSceneMode = sceneEventProgress.LoadSceneMode,
ClientsThatCompleted = sceneEventProgress.DoneClients,
ClientsThatTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList(),
ClientsThatCompleted = clientsThatCompleted,
ClientsThatTimedOut = clientsThatTimedOut,
});

if (sceneEventData.SceneEventType == SceneEventType.LoadEventCompleted)
Expand Down Expand Up @@ -1070,7 +1072,7 @@ private void OnSceneUnloaded(uint sceneEventId)
//Only if we are a host do we want register having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
{
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId);
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.ServerClientId);
}
}

Expand Down Expand Up @@ -1488,7 +1490,7 @@ private void OnServerLoadedScene(uint sceneEventId, Scene scene)
//Second, only if we are a host do we want register having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
{
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId);
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.ServerClientId);
}
EndSceneEvent(sceneEventId);
}
Expand Down Expand Up @@ -1880,7 +1882,7 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId)

if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
{
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(clientId);
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(clientId);
}
EndSceneEvent(sceneEventId);
break;
Expand All @@ -1889,7 +1891,7 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId)
{
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
{
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(clientId);
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(clientId);
}
// Notify the local server that the client has finished unloading a scene
OnSceneEvent?.Invoke(new SceneEvent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,13 @@ internal class SceneEventProgress
/// <summary>
/// List of clientIds of those clients that is done loading the scene.
/// </summary>
internal List<ulong> DoneClients { get; } = new List<ulong>();
internal Dictionary<ulong, bool> ClientsProcessingSceneEvent { get; } = new Dictionary<ulong, bool>();
internal List<ulong> ClientsThatDisconnected = new List<ulong>();

/// <summary>
/// The local time when the scene event was "roughly started"
/// This is the time when the current scene event will have timed out
/// </summary>
internal float TimeAtInitiation { get; }
internal float WhenSceneEventHasTimedOut;

/// <summary>
/// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out.
Expand All @@ -76,16 +77,17 @@ internal class SceneEventProgress
internal OnCompletedDelegate OnComplete;

/// <summary>
/// Is this scene switch progresses completed, all clients are done loading the scene or a timeout has occurred.
/// When set the scene event is considered completed locally
/// </summary>
internal bool IsCompleted { get; private set; }

internal bool TimedOut { get; private set; }
internal bool IsSceneEventCompleted { get; private set; }

/// <summary>
/// If all clients are done loading the scene, at the moment of completed.
/// This will make sure that we only have timed out if we never completed
/// </summary>
internal bool AreAllClientsDoneLoading { get; private set; }
internal bool HasTimedOut()
{
return WhenSceneEventHasTimedOut <= Time.realtimeSinceStartup;
}

/// <summary>
/// The hash value generated from the full scene path
Expand All @@ -105,21 +107,57 @@ internal class SceneEventProgress

internal LoadSceneMode LoadSceneMode;

internal List<ulong> ClientsThatStartedSceneEvent;
internal List<ulong> GetClientsWithStatus(bool completedSceneEvent)
{
var clients = new List<ulong>();
foreach (var clientStatus in ClientsProcessingSceneEvent)
{
if (clientStatus.Value == completedSceneEvent)
{
clients.Add(clientStatus.Key);
}
}

// If we are getting the list of clients that have not completed the
// scene event, then add any clients that disconnected during this
// scene event.
if (!completedSceneEvent)
{
clients.AddRange(ClientsThatDisconnected);
}
return clients;
}

internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressStatus status = SceneEventProgressStatus.Started)
{
if (status == SceneEventProgressStatus.Started)
{
// Track the clients that were connected when we started this event
ClientsThatStartedSceneEvent = new List<ulong>(networkManager.ConnectedClientsIds);
foreach (var connectedClientId in networkManager.ConnectedClientsIds)
{
ClientsProcessingSceneEvent.Add(connectedClientId, false);
}
m_NetworkManager = networkManager;
m_NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
WhenSceneEventHasTimedOut = Time.realtimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut;
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
TimeAtInitiation = Time.realtimeSinceStartup;
}
Status = status;
}

/// <summary>
/// Remove the client from the clients processing the current scene event
/// Add this client to the clients that disconnected list
/// </summary>
private void OnClientDisconnectCallback(ulong clientId)
{
if (ClientsProcessingSceneEvent.ContainsKey(clientId))
{
ClientsThatDisconnected.Add(clientId);
ClientsProcessingSceneEvent.Remove(clientId);
}
}

/// <summary>
/// Coroutine that checks to see if the scene event is complete every network tick period.
/// This will handle completing the scene event when one or more client(s) disconnect(s)
Expand All @@ -129,34 +167,46 @@ internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressSta
internal IEnumerator TimeOutSceneEventProgress()
{
var waitForNetworkTick = new WaitForSeconds(1.0f / m_NetworkManager.NetworkConfig.TickRate);
while (!TimedOut && !IsCompleted)
while (!HasTimedOut())
{
yield return waitForNetworkTick;

CheckCompletion();
if (!IsCompleted)
{
TimedOut = TimeAtInitiation - Time.realtimeSinceStartup >= m_NetworkManager.NetworkConfig.LoadSceneTimeOut;
}
TryFinishingSceneEventProgress();
}
}

internal void AddClientAsDone(ulong clientId)
/// <summary>
/// Sets the client's scene event progress to finished/true
/// </summary>
internal void ClientFinishedSceneEvent(ulong clientId)
{
DoneClients.Add(clientId);
CheckCompletion();
if (ClientsProcessingSceneEvent.ContainsKey(clientId))
{
ClientsProcessingSceneEvent[clientId] = true;
TryFinishingSceneEventProgress();
}
}

internal void RemoveClientAsDone(ulong clientId)
/// <summary>
/// Checks if all known clients processing this scene event
/// are finished.
/// </summary>
private bool AreAllClientsFinished()
{
DoneClients.Remove(clientId);
CheckCompletion();
foreach (var clientStatus in ClientsProcessingSceneEvent)
{
if (!clientStatus.Value)
{
return false;
}
}
return true;
}

internal void SetSceneLoadOperation(AsyncOperation sceneLoadOperation)
{
m_SceneLoadOperation = sceneLoadOperation;
m_SceneLoadOperation.completed += operation => CheckCompletion();
m_SceneLoadOperation.completed += operation => TryFinishingSceneEventProgress();
}

/// <summary>
Expand All @@ -171,37 +221,36 @@ internal void SetSceneLoadOperation(AsyncOperation sceneLoadOperation)
/// </summary>
internal void SetSceneLoadOperation(ISceneManagerHandler.SceneEventAction sceneEventAction)
{
sceneEventAction.Completed = SetComplete;
sceneEventAction.Completed = SetLoadCompleted;
Comment thread
NoelStephensUnity marked this conversation as resolved.
Outdated
}

/// <summary>
/// Finalizes the SceneEventProgress
/// For integration tests
/// </summary>
internal void SetComplete()
internal void SetLoadCompleted()
{
IsCompleted = true;
AreAllClientsDoneLoading = true;

// If OnComplete is not registered or it is and returns true then remove this from the progress tracking
if (OnComplete == null || (OnComplete != null && OnComplete.Invoke(this)))
{
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
}
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
IsSceneEventCompleted = true;
TryFinishingSceneEventProgress();
}

internal void CheckCompletion()
/// <summary>
/// Will try to finish the current scene event in progress as long as all conditions are met.
/// </summary>
internal void TryFinishingSceneEventProgress()
{
try
// If all of our clients are finished loading and the local m_SceneLoadOperation is complete or
// we have timed out, then finish this SceneEventProgress
if (m_SceneLoadOperation != null && !IsSceneEventCompleted)
{
if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && (m_SceneLoadOperation == null || m_SceneLoadOperation.isDone)) || (!IsCompleted && TimedOut))
{
SetComplete();
}
IsSceneEventCompleted = m_SceneLoadOperation.isDone;
}
catch (Exception ex)

if (AreAllClientsFinished() && (IsSceneEventCompleted || HasTimedOut()))
{
Debug.LogException(ex);
OnComplete?.Invoke(this);
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
m_NetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback;
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
}
}
}
Expand Down