using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using AsyncOperation = UnityEngine.AsyncOperation;
namespace Unity.Netcode
{
///
/// Used by to determine if a server invoked scene event has started.
/// The returned status is stored in the property.
/// Note: This was formally known as SwitchSceneProgress which contained the .
/// All s are now delivered by the event handler
/// via the parameter.
///
public enum SceneEventProgressStatus
{
///
/// No scene event progress status can be used to initialize a variable that will be checked over time.
///
None,
///
/// The scene event was successfully started.
///
Started,
///
/// Returned if you try to unload a scene that was not yet loaded.
///
SceneNotLoaded,
///
/// Returned if you try to start a new scene event before a previous one is finished.
///
SceneEventInProgress,
///
/// Returned if the scene name used with
/// or is invalid.
///
InvalidSceneName,
///
/// Server side: Returned if the delegate handler returns false
/// (i.e. scene is considered not valid/safe to load).
///
SceneFailedVerification,
///
/// This is used for internal error notifications.
/// If you receive this event then it is most likely due to a bug (please open a GitHub issue with steps to replicate).
///
InternalNetcodeError,
}
///
/// Server side only:
/// This tracks the progress of clients during a load or unload scene event
///
internal class SceneEventProgress
{
///
/// List of clientIds of those clients that is done loading the scene.
///
internal List DoneClients { get; } = new List();
///
/// The local time when the scene event was "roughly started"
///
internal float TimeAtInitiation { get; }
///
/// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out.
///
internal delegate bool OnCompletedDelegate(SceneEventProgress sceneEventProgress);
///
/// The callback invoked when the switch scene progress is completed. Either by all clients done loading the scene or by time out.
///
internal OnCompletedDelegate OnComplete;
///
/// Is this scene switch progresses completed, all clients are done loading the scene or a timeout has occurred.
///
internal bool IsCompleted { get; private set; }
internal bool TimedOut { get; private set; }
///
/// If all clients are done loading the scene, at the moment of completed.
///
internal bool AreAllClientsDoneLoading { get; private set; }
///
/// The hash value generated from the full scene path
///
internal uint SceneHash { get; set; }
internal Guid Guid { get; } = Guid.NewGuid();
private Coroutine m_TimeOutCoroutine;
private AsyncOperation m_SceneLoadOperation;
private NetworkManager m_NetworkManager { get; }
internal SceneEventProgressStatus Status { get; set; }
internal SceneEventType SceneEventType { get; set; }
internal LoadSceneMode LoadSceneMode;
internal List ClientsThatStartedSceneEvent;
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(networkManager.ConnectedClientsIds);
m_NetworkManager = networkManager;
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
TimeAtInitiation = Time.realtimeSinceStartup;
}
Status = status;
}
///
/// 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)
/// during a scene event and if it does not complete within the scene loading time out period
/// it will time out the scene event.
///
internal IEnumerator TimeOutSceneEventProgress()
{
var waitForNetworkTick = new WaitForSeconds(1.0f / m_NetworkManager.NetworkConfig.TickRate);
while (!TimedOut && !IsCompleted)
{
yield return waitForNetworkTick;
CheckCompletion();
if (!IsCompleted)
{
TimedOut = TimeAtInitiation - Time.realtimeSinceStartup >= m_NetworkManager.NetworkConfig.LoadSceneTimeOut;
}
}
}
internal void AddClientAsDone(ulong clientId)
{
DoneClients.Add(clientId);
CheckCompletion();
}
internal void RemoveClientAsDone(ulong clientId)
{
DoneClients.Remove(clientId);
CheckCompletion();
}
internal void SetSceneLoadOperation(AsyncOperation sceneLoadOperation)
{
m_SceneLoadOperation = sceneLoadOperation;
m_SceneLoadOperation.completed += operation => CheckCompletion();
}
///
/// Called only on the server-side during integration test (NetcodeIntegrationTest specific)
/// scene loading and unloading.
///
/// Note: During integration testing we must queue all scene loading and unloading requests for
/// both the server and all clients so they can be processed in a FIFO/linear fashion to avoid
/// conflicts when the and
/// events are triggered. The Completed action simulates the event.
/// (See: Unity.Netcode.TestHelpers.Runtime.IntegrationTestSceneHandler)
///
internal void SetSceneLoadOperation(ISceneManagerHandler.SceneEventAction sceneEventAction)
{
sceneEventAction.Completed = SetComplete;
}
///
/// Finalizes the SceneEventProgress
///
internal void SetComplete()
{
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);
}
internal void CheckCompletion()
{
try
{
if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && (m_SceneLoadOperation == null || m_SceneLoadOperation.isDone)) || (!IsCompleted && TimedOut))
{
SetComplete();
}
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
}
}