com.unity.netcode.gameobjects@1.0.0-pre.10

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.0.0-pre.10] - 2022-06-21

### Added

- Added a new `OnTransportFailure` callback to `NetworkManager`. This callback is invoked when the manager's `NetworkTransport` encounters an unrecoverable error. Transport failures also cause the `NetworkManager` to shut down. Currently, this is only used by `UnityTransport` to signal a timeout of its connection to the Unity Relay servers. (#1994)
- Added `NetworkEvent.TransportFailure`, which can be used by implementations of `NetworkTransport` to signal to `NetworkManager` that an unrecoverable error was encountered. (#1994)
- Added test to ensure a warning occurs when nesting NetworkObjects in a NetworkPrefab (#1969)
- Added `NetworkManager.RemoveNetworkPrefab(...)` to remove a prefab from the prefabs list (#1950)

### Changed

- Updated `UnityTransport` dependency on `com.unity.transport` to 1.1.0. (#2025)
- (API Breaking) `ConnectionApprovalCallback` is no longer an `event` and will not allow more than 1 handler registered at a time. Also, `ConnectionApprovalCallback` is now a `Func<>` taking `ConnectionApprovalRequest` in and returning `ConnectionApprovalResponse` back out (#1972)

### Removed

### Fixed
- Fixed issue where dynamically spawned `NetworkObject`s could throw an exception if the scene of origin handle was zero (0) and the `NetworkObject` was already spawned. (#2017)
- Fixed issue where `NetworkObject.Observers` was not being cleared when despawned. (#2009)
- Fixed `NetworkAnimator` could not run in the server authoritative mode. (#2003)
- Fixed issue where late joining clients would get a soft synchronization error if any in-scene placed NetworkObjects were parented under another `NetworkObject`. (#1985)
- Fixed issue where `NetworkBehaviourReference` would throw a type cast exception if using `NetworkBehaviourReference.TryGet` and the component type was not found. (#1984)
- Fixed `NetworkSceneManager` was not sending scene event notifications for the currently active scene and any additively loaded scenes when loading a new scene in `LoadSceneMode.Single` mode. (#1975)
- Fixed issue where one or more clients disconnecting during a scene event would cause `LoadEventCompleted` or `UnloadEventCompleted` to wait until the `NetworkConfig.LoadSceneTimeOut` period before being triggered. (#1973)
- Fixed issues when multiple `ConnectionApprovalCallback`s were registered (#1972)
- Fixed a regression in serialization support: `FixedString`, `Vector2Int`, and `Vector3Int` types can now be used in NetworkVariables and RPCs again without requiring a `ForceNetworkSerializeByMemcpy<>` wrapper. (#1961)
- Fixed generic types that inherit from NetworkBehaviour causing crashes at compile time. (#1976)
- Fixed endless dialog boxes when adding a `NetworkBehaviour` to a `NetworkManager` or vice-versa. (#1947)
- Fixed `NetworkAnimator` issue where it was only synchronizing parameters if the layer or state changed or was transitioning between states. (#1946)
- Fixed `NetworkAnimator` issue where when it did detect a parameter had changed it would send all parameters as opposed to only the parameters that changed. (#1946)
- Fixed `NetworkAnimator` issue where it was not always disposing the `NativeArray` that is allocated when spawned. (#1946)
- Fixed `NetworkAnimator` issue where it was not taking the animation speed or state speed multiplier into consideration. (#1946)
- Fixed `NetworkAnimator` issue where it was not properly synchronizing late joining clients if they joined while `Animator` was transitioning between states. (#1946)
- Fixed `NetworkAnimator` issue where the server was not relaying changes to non-owner clients when a client was the owner. (#1946)
- Fixed issue where the `PacketLoss` metric for tools would return the packet loss over a connection lifetime instead of a single frame. (#2004)
This commit is contained in:
Unity Technologies
2022-06-21 00:00:00 +00:00
parent 5b1fc203ed
commit 0f7a30d285
62 changed files with 3763 additions and 1286 deletions

View File

@@ -1,4 +1,4 @@
using System;
namespace Unity.Netcode.TestHelpers.Runtime
{
public class ObjectNameIdentifier : NetworkBehaviour
@@ -7,36 +7,46 @@ namespace Unity.Netcode.TestHelpers.Runtime
private ulong m_CurrentNetworkObjectId;
private bool m_IsRegistered;
private const char k_TagInfoStart = '{';
private const char k_TagInfoStop = '}';
/// <summary>
/// Keep a reference to the assigned NetworkObject
/// <see cref="OnDestroy"/>
/// </summary>
[NonSerialized]
private NetworkObject m_NetworkObject;
private string m_OriginalName;
public override void OnNetworkSpawn()
{
RegisterAndLabelNetworkObject();
}
protected void RegisterAndLabelNetworkObject()
{
if (!m_IsRegistered)
{
if (string.IsNullOrEmpty(m_OriginalName))
{
m_OriginalName = gameObject.name.Replace("(Clone)", "");
}
// This is required otherwise it will try to continue to update the NetworkBehaviour even if
// it has been destroyed.
m_NetworkObject = NetworkObject;
m_CurrentOwner = OwnerClientId;
m_CurrentNetworkObjectId = NetworkObjectId;
var objectOriginalName = gameObject.name.Replace("(Clone)", "");
var serverOrClient = IsServer ? "Server" : "Client";
if (NetworkObject.IsPlayerObject)
{
gameObject.name = NetworkManager.LocalClientId == OwnerClientId ? $"{objectOriginalName}({OwnerClientId})-Local{objectOriginalName}" :
$"{objectOriginalName}({OwnerClientId})-On{serverOrClient}({NetworkManager.LocalClientId})";
gameObject.name = NetworkManager.LocalClientId == OwnerClientId ? $"{m_OriginalName}-{k_TagInfoStart}{OwnerClientId}{k_TagInfoStop}-Local{m_OriginalName}" :
$"{m_OriginalName}-{k_TagInfoStart}{OwnerClientId}{k_TagInfoStop}- On{serverOrClient}{k_TagInfoStart}{NetworkManager.LocalClientId}{k_TagInfoStop}";
}
else
{
gameObject.name = $"{objectOriginalName}({NetworkObjectId})-On{serverOrClient}({NetworkManager.LocalClientId})";
gameObject.name = $"{m_OriginalName}{k_TagInfoStart}{NetworkObjectId}{k_TagInfoStop}-On{serverOrClient}{k_TagInfoStart}{NetworkManager.LocalClientId}{k_TagInfoStop}";
}
// Don't add the player objects to the global list of NetworkObjects

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
@@ -9,22 +10,51 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
/// <summary>
/// The default SceneManagerHandler used for all NetcodeIntegrationTest derived children.
/// This enables clients to load scenes within the same scene hierarchy during integration
/// testing.
/// </summary>
internal class IntegrationTestSceneHandler : ISceneManagerHandler, IDisposable
{
internal CoroutineRunner CoroutineRunner;
// All IntegrationTestSceneHandler instances register their associated NetworkManager
internal static List<NetworkManager> NetworkManagers = new List<NetworkManager>();
// Default client simulated delay time
protected const float k_ClientLoadingSimulatedDelay = 0.02f;
internal static CoroutineRunner CoroutineRunner;
internal static Queue<QueuedSceneJob> QueuedSceneJobs = new Queue<QueuedSceneJob>();
internal List<Coroutine> CoroutinesRunning = new List<Coroutine>();
internal static Coroutine SceneJobProcessor;
internal static QueuedSceneJob CurrentQueuedSceneJob;
protected static WaitForSeconds s_WaitForSeconds;
// Controls the client simulated delay time
protected float m_ClientLoadingSimulatedDelay = k_ClientLoadingSimulatedDelay;
public delegate bool CanClientsLoadUnloadDelegateHandler();
public event CanClientsLoadUnloadDelegateHandler CanClientsLoad;
public event CanClientsLoadUnloadDelegateHandler CanClientsUnload;
public static event CanClientsLoadUnloadDelegateHandler CanClientsLoad;
public static event CanClientsLoadUnloadDelegateHandler CanClientsUnload;
internal List<Coroutine> CoroutinesRunning = new List<Coroutine>();
public static bool VerboseDebugMode;
/// <summary>
/// Used for loading scenes on the client-side during
/// an integration test
/// </summary>
internal class QueuedSceneJob
{
public enum JobTypes
{
Loading,
Unloading,
Completed
}
public JobTypes JobType;
public string SceneName;
public Scene Scene;
public ISceneManagerHandler.SceneEventAction SceneAction;
public IntegrationTestSceneHandler IntegrationTestSceneHandler;
}
internal NetworkManager NetworkManager;
internal string NetworkManagerName;
/// <summary>
/// Used to control when clients should attempt to fake-load a scene
@@ -44,19 +74,6 @@ namespace Unity.Netcode.TestHelpers.Runtime
return true;
}
/// <summary>
/// Fake-Loads a scene for a client
/// </summary>
internal IEnumerator ClientLoadSceneCoroutine(string sceneName, ISceneManagerHandler.SceneEventAction sceneEventAction)
{
yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay);
while (!OnCanClientsLoad())
{
yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay);
}
sceneEventAction.Invoke();
}
protected bool OnCanClientsUnload()
{
if (CanClientsUnload != null)
@@ -66,35 +83,298 @@ namespace Unity.Netcode.TestHelpers.Runtime
return true;
}
/// <summary>
/// Fake-Unloads a scene for a client
/// </summary>
internal IEnumerator ClientUnloadSceneCoroutine(ISceneManagerHandler.SceneEventAction sceneEventAction)
internal static void VerboseDebug(string message)
{
yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay);
while (!OnCanClientsUnload())
if (VerboseDebugMode)
{
yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay);
Debug.Log(message);
}
sceneEventAction.Invoke();
}
/// <summary>
/// Processes scene loading jobs
/// </summary>
/// <param name="queuedSceneJob">job to process</param>
static internal IEnumerator ProcessLoadingSceneJob(QueuedSceneJob queuedSceneJob)
{
var itegrationTestSceneHandler = queuedSceneJob.IntegrationTestSceneHandler;
while (!itegrationTestSceneHandler.OnCanClientsLoad())
{
yield return s_WaitForSeconds;
}
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
// We always load additively for all scenes during integration tests
SceneManager.LoadSceneAsync(queuedSceneJob.SceneName, LoadSceneMode.Additive);
// Wait for it to finish
while (queuedSceneJob.JobType != QueuedSceneJob.JobTypes.Completed)
{
yield return s_WaitForSeconds;
}
yield return s_WaitForSeconds;
CurrentQueuedSceneJob.SceneAction.Invoke();
}
/// <summary>
/// Handles scene loading and assists with making sure the right NetworkManagerOwner
/// is assigned to newly instantiated NetworkObjects.
///
/// Note: Static property usage is OK since jobs are processed one at a time
/// </summary>
private static void SceneManager_sceneLoaded(Scene scene, LoadSceneMode loadSceneMode)
{
if (CurrentQueuedSceneJob.JobType != QueuedSceneJob.JobTypes.Completed && CurrentQueuedSceneJob.SceneName == scene.name)
{
SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
ProcessInSceneObjects(scene, CurrentQueuedSceneJob.IntegrationTestSceneHandler.NetworkManager);
CurrentQueuedSceneJob.JobType = QueuedSceneJob.JobTypes.Completed;
}
}
/// <summary>
/// Handles some pre-spawn processing of in-scene placed NetworkObjects
/// to make sure the appropriate NetworkManagerOwner is assigned. It
/// also makes sure that each in-scene placed NetworkObject has an
/// ObjectIdentifier component if one is not assigned to it or its
/// children.
/// </summary>
/// <param name="scene">the scenes that was just loaded</param>
/// <param name="networkManager">the relative NetworkManager</param>
private static void ProcessInSceneObjects(Scene scene, NetworkManager networkManager)
{
// Get all in-scene placed NeworkObjects that were instantiated when this scene loaded
var inSceneNetworkObjects = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsSceneObject != false && c.GetSceneOriginHandle() == scene.handle);
foreach (var sobj in inSceneNetworkObjects)
{
if (sobj.NetworkManagerOwner != networkManager)
{
sobj.NetworkManagerOwner = networkManager;
}
if (sobj.GetComponent<ObjectNameIdentifier>() == null && sobj.GetComponentInChildren<ObjectNameIdentifier>() == null)
{
sobj.gameObject.AddComponent<ObjectNameIdentifier>();
}
}
}
/// <summary>
/// Processes scene unloading jobs
/// </summary>
/// <param name="queuedSceneJob">job to process</param>
static internal IEnumerator ProcessUnloadingSceneJob(QueuedSceneJob queuedSceneJob)
{
var itegrationTestSceneHandler = queuedSceneJob.IntegrationTestSceneHandler;
while (!itegrationTestSceneHandler.OnCanClientsUnload())
{
yield return s_WaitForSeconds;
}
SceneManager.sceneUnloaded += SceneManager_sceneUnloaded;
if (queuedSceneJob.Scene.IsValid() && queuedSceneJob.Scene.isLoaded && !queuedSceneJob.Scene.name.Contains(NetcodeIntegrationTestHelpers.FirstPartOfTestRunnerSceneName))
{
SceneManager.UnloadSceneAsync(queuedSceneJob.Scene);
}
else
{
CurrentQueuedSceneJob.JobType = QueuedSceneJob.JobTypes.Completed;
}
// Wait for it to finish
while (queuedSceneJob.JobType != QueuedSceneJob.JobTypes.Completed)
{
yield return s_WaitForSeconds;
}
CurrentQueuedSceneJob.SceneAction.Invoke();
}
/// <summary>
/// Handles closing out scene unloading jobs
/// </summary>
private static void SceneManager_sceneUnloaded(Scene scene)
{
if (CurrentQueuedSceneJob.JobType != QueuedSceneJob.JobTypes.Completed && CurrentQueuedSceneJob.Scene.name == scene.name)
{
SceneManager.sceneUnloaded -= SceneManager_sceneUnloaded;
CurrentQueuedSceneJob.JobType = QueuedSceneJob.JobTypes.Completed;
}
}
/// <summary>
/// Processes all jobs within the queue.
/// When all jobs are finished, the coroutine stops.
/// </summary>
static internal IEnumerator JobQueueProcessor()
{
while (QueuedSceneJobs.Count != 0)
{
CurrentQueuedSceneJob = QueuedSceneJobs.Dequeue();
VerboseDebug($"[ITSH-START] {CurrentQueuedSceneJob.IntegrationTestSceneHandler.NetworkManagerName} processing {CurrentQueuedSceneJob.JobType} for scene {CurrentQueuedSceneJob.SceneName}.");
if (CurrentQueuedSceneJob.JobType == QueuedSceneJob.JobTypes.Loading)
{
yield return ProcessLoadingSceneJob(CurrentQueuedSceneJob);
}
else if (CurrentQueuedSceneJob.JobType == QueuedSceneJob.JobTypes.Unloading)
{
yield return ProcessUnloadingSceneJob(CurrentQueuedSceneJob);
}
VerboseDebug($"[ITSH-STOP] {CurrentQueuedSceneJob.IntegrationTestSceneHandler.NetworkManagerName} processing {CurrentQueuedSceneJob.JobType} for scene {CurrentQueuedSceneJob.SceneName}.");
}
SceneJobProcessor = null;
yield break;
}
/// <summary>
/// Adds a job to the job queue, and if the JobQueueProcessor coroutine
/// is not running then it will be started as well.
/// </summary>
/// <param name="queuedSceneJob">job to add to the queue</param>
private void AddJobToQueue(QueuedSceneJob queuedSceneJob)
{
QueuedSceneJobs.Enqueue(queuedSceneJob);
if (SceneJobProcessor == null)
{
SceneJobProcessor = CoroutineRunner.StartCoroutine(JobQueueProcessor());
}
}
private string m_ServerSceneBeingLoaded;
/// <summary>
/// Server always loads like it normally would
/// </summary>
public AsyncOperation GenericLoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction)
{
m_ServerSceneBeingLoaded = sceneName;
if (NetcodeIntegrationTest.IsRunning)
{
SceneManager.sceneLoaded += Sever_SceneLoaded;
}
var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode);
operation.completed += new Action<AsyncOperation>(asyncOp2 => { sceneEventAction.Invoke(); });
return operation;
}
private void Sever_SceneLoaded(Scene scene, LoadSceneMode arg1)
{
if (m_ServerSceneBeingLoaded == scene.name)
{
ProcessInSceneObjects(scene, NetworkManager);
SceneManager.sceneLoaded -= Sever_SceneLoaded;
}
}
/// <summary>
/// Server always unloads like it normally would
/// </summary>
public AsyncOperation GenericUnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction)
{
var operation = SceneManager.UnloadSceneAsync(scene);
operation.completed += new Action<AsyncOperation>(asyncOp2 => { sceneEventAction.Invoke(); });
return operation;
}
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction)
{
CoroutinesRunning.Add(CoroutineRunner.StartCoroutine(ClientLoadSceneCoroutine(sceneName, sceneEventAction)));
// This is OK to return a "nothing" AsyncOperation since we are simulating client loading
return new AsyncOperation();
// Server and non NetcodeIntegrationTest tests use the generic load scene method
if (!NetcodeIntegrationTest.IsRunning)
{
return GenericLoadSceneAsync(sceneName, loadSceneMode, sceneEventAction);
}
else // NetcodeIntegrationTest Clients always get added to the jobs queue
{
AddJobToQueue(new QueuedSceneJob() { IntegrationTestSceneHandler = this, SceneName = sceneName, SceneAction = sceneEventAction, JobType = QueuedSceneJob.JobTypes.Loading });
}
return null;
}
public AsyncOperation UnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction)
{
CoroutinesRunning.Add(CoroutineRunner.StartCoroutine(ClientUnloadSceneCoroutine(sceneEventAction)));
// Server and non NetcodeIntegrationTest tests use the generic unload scene method
if (!NetcodeIntegrationTest.IsRunning)
{
return GenericUnloadSceneAsync(scene, sceneEventAction);
}
else // NetcodeIntegrationTest Clients always get added to the jobs queue
{
AddJobToQueue(new QueuedSceneJob() { IntegrationTestSceneHandler = this, Scene = scene, SceneAction = sceneEventAction, JobType = QueuedSceneJob.JobTypes.Unloading });
}
// This is OK to return a "nothing" AsyncOperation since we are simulating client loading
return new AsyncOperation();
return null;
}
public IntegrationTestSceneHandler()
/// <summary>
/// Replacement callback takes other NetworkManagers into consideration
/// </summary>
internal Scene GetAndAddNewlyLoadedSceneByName(string sceneName)
{
for (int i = 0; i < SceneManager.sceneCount; i++)
{
var sceneLoaded = SceneManager.GetSceneAt(i);
if (sceneLoaded.name == sceneName)
{
var skip = false;
foreach (var networkManager in NetworkManagers)
{
if (NetworkManager.LocalClientId == networkManager.LocalClientId || !networkManager.IsListening)
{
continue;
}
if (networkManager.SceneManager.ScenesLoaded.ContainsKey(sceneLoaded.handle))
{
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogInfo($"{NetworkManager.name}'s ScenesLoaded contains {sceneLoaded.name} with a handle of {sceneLoaded.handle}. Skipping over scene.");
}
skip = true;
break;
}
}
if (!skip && !NetworkManager.SceneManager.ScenesLoaded.ContainsKey(sceneLoaded.handle))
{
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogInfo($"{NetworkManager.name} adding {sceneLoaded.name} with a handle of {sceneLoaded.handle} to its ScenesLoaded.");
}
NetworkManager.SceneManager.ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
return sceneLoaded;
}
}
}
throw new Exception($"Failed to find any loaded scene named {sceneName}!");
}
private bool ExcludeSceneFromSynchronizationCheck(Scene scene)
{
if (!NetworkManager.SceneManager.ScenesLoaded.ContainsKey(scene.handle) && SceneManager.GetActiveScene().handle != scene.handle)
{
return false;
}
return true;
}
/// <summary>
/// Constructor now must take NetworkManager
/// </summary>
public IntegrationTestSceneHandler(NetworkManager networkManager)
{
networkManager.SceneManager.OverrideGetAndAddNewlyLoadedSceneByName = GetAndAddNewlyLoadedSceneByName;
networkManager.SceneManager.ExcludeSceneFromSychronization = ExcludeSceneFromSynchronizationCheck;
NetworkManagers.Add(networkManager);
NetworkManagerName = networkManager.name;
if (s_WaitForSeconds == null)
{
s_WaitForSeconds = new WaitForSeconds(1.0f / networkManager.NetworkConfig.TickRate);
}
NetworkManager = networkManager;
if (CoroutineRunner == null)
{
CoroutineRunner = new GameObject("UnitTestSceneHandlerCoroutine").AddComponent<CoroutineRunner>();
@@ -103,12 +383,29 @@ namespace Unity.Netcode.TestHelpers.Runtime
public void Dispose()
{
foreach (var coroutine in CoroutinesRunning)
NetworkManagers.Clear();
if (SceneJobProcessor != null)
{
CoroutineRunner.StopCoroutine(coroutine);
CoroutineRunner.StopCoroutine(SceneJobProcessor);
SceneJobProcessor = null;
}
CoroutineRunner.StopAllCoroutines();
foreach (var job in QueuedSceneJobs)
{
if (job.JobType != QueuedSceneJob.JobTypes.Completed)
{
if (job.JobType == QueuedSceneJob.JobTypes.Loading)
{
SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
}
else
{
SceneManager.sceneUnloaded -= SceneManager_sceneUnloaded;
}
job.JobType = QueuedSceneJob.JobTypes.Completed;
}
}
QueuedSceneJobs.Clear();
Object.Destroy(CoroutineRunner.gameObject);
}
}

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using System.Runtime.CompilerServices;
@@ -16,7 +17,12 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
public abstract class NetcodeIntegrationTest
{
protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(4.0f);
/// <summary>
/// Used to determine if a NetcodeIntegrationTest is currently running to
/// determine how clients will load scenes
/// </summary>
internal static bool IsRunning { get; private set; }
protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(8.0f);
protected static WaitForSeconds s_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate);
/// <summary>
@@ -74,9 +80,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
}
}
protected int TotalClients => m_UseHost ? NumberOfClients + 1 : NumberOfClients;
protected int TotalClients => m_UseHost ? m_NumberOfClients + 1 : m_NumberOfClients;
protected const uint k_DefaultTickRate = 30;
private int m_NumberOfClients;
protected abstract int NumberOfClients { get; }
/// <summary>
@@ -119,7 +127,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
private NetworkManagerInstatiationMode m_NetworkManagerInstatiationMode;
private bool m_EnableVerboseDebug;
protected bool m_EnableVerboseDebug { get; set; }
/// <summary>
/// Used to display the various integration test
@@ -165,8 +173,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
[OneTimeSetUp]
public void OneTimeSetup()
{
Application.runInBackground = true;
m_NumberOfClients = NumberOfClients;
IsRunning = true;
m_EnableVerboseDebug = OnSetVerboseDebug();
IntegrationTestSceneHandler.VerboseDebugMode = m_EnableVerboseDebug;
VerboseDebug($"Entering {nameof(OneTimeSetup)}");
m_NetworkManagerInstatiationMode = OnSetIntegrationTestMode();
@@ -248,6 +259,61 @@ namespace Unity.Netcode.TestHelpers.Runtime
CreateServerAndClients(NumberOfClients);
}
protected virtual void OnNewClientCreated(NetworkManager networkManager)
{
}
protected virtual void OnNewClientStartedAndConnected(NetworkManager networkManager)
{
}
private void AddRemoveNetworkManager(NetworkManager networkManager, bool addNetworkManager)
{
var clientNetworkManagersList = new List<NetworkManager>(m_ClientNetworkManagers);
if (addNetworkManager)
{
clientNetworkManagersList.Add(networkManager);
}
else
{
clientNetworkManagersList.Remove(networkManager);
}
m_ClientNetworkManagers = clientNetworkManagersList.ToArray();
m_NumberOfClients = clientNetworkManagersList.Count;
}
protected IEnumerator CreateAndStartNewClient()
{
var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length);
networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
// Notification that the new client (NetworkManager) has been created
// in the event any modifications need to be made before starting the client
OnNewClientCreated(networkManager);
NetcodeIntegrationTestHelpers.StartOneClient(networkManager);
AddRemoveNetworkManager(networkManager, true);
// Wait for the new client to connect
yield return WaitForClientsConnectedOrTimeOut();
if (s_GlobalTimeoutHelper.TimedOut)
{
AddRemoveNetworkManager(networkManager, false);
Object.Destroy(networkManager.gameObject);
}
AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for the new client to be connected!");
ClientNetworkManagerPostStart(networkManager);
VerboseDebug($"[{networkManager.name}] Created and connected!");
}
protected IEnumerator StopOneClient(NetworkManager networkManager, bool destroy = false)
{
NetcodeIntegrationTestHelpers.StopOneClient(networkManager, destroy);
AddRemoveNetworkManager(networkManager, false);
yield return WaitForConditionOrTimeOut(() => !networkManager.IsConnectedClient);
}
/// <summary>
/// Creates the server and clients
/// </summary>
@@ -315,6 +381,54 @@ namespace Unity.Netcode.TestHelpers.Runtime
yield return null;
}
private void ClientNetworkManagerPostStart(NetworkManager networkManager)
{
networkManager.name = $"NetworkManager - Client - {networkManager.LocalClientId}";
Assert.NotNull(networkManager.LocalClient.PlayerObject, $"{nameof(StartServerAndClients)} detected that client {networkManager.LocalClientId} does not have an assigned player NetworkObject!");
// Get all player instances for the current client NetworkManager instance
var clientPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == networkManager.LocalClientId);
// Add this player instance to each client player entry
foreach (var playerNetworkObject in clientPlayerClones)
{
// When the server is not the host this needs to be done
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
{
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
}
if (!m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].ContainsKey(networkManager.LocalClientId))
{
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(networkManager.LocalClientId, playerNetworkObject);
}
}
}
protected void ClientNetworkManagerPostStartInit()
{
// Creates a dictionary for all player instances client and server relative
// This provides a simpler way to get a specific player instance relative to a client instance
foreach (var networkManager in m_ClientNetworkManagers)
{
ClientNetworkManagerPostStart(networkManager);
}
if (m_UseHost)
{
var clientSideServerPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId);
foreach (var playerNetworkObject in clientSideServerPlayerClones)
{
// When the server is not the host this needs to be done
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
{
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
}
if (!m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].ContainsKey(m_ServerNetworkManager.LocalClientId))
{
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject);
}
}
}
}
/// <summary>
/// This starts the server and clients as long as <see cref="CanStartServerAndClients"/>
/// returns true.
@@ -340,8 +454,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
// Wait for all clients to connect
yield return WaitForClientsConnectedOrTimeOut();
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!");
AssertOnTimeout($"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!");
if (m_UseHost || m_ServerNetworkManager.IsHost)
{
@@ -357,25 +470,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
}
}
// Creates a dictionary for all player instances client and server relative
// This provides a simpler way to get a specific player instance relative to a client instance
foreach (var networkManager in m_ClientNetworkManagers)
{
Assert.NotNull(networkManager.LocalClient.PlayerObject, $"{nameof(StartServerAndClients)} detected that client {networkManager.LocalClientId} does not have an assigned player NetworkObject!");
// Get all player instances for the current client NetworkManager instance
var clientPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == networkManager.LocalClientId);
// Add this player instance to each client player entry
foreach (var playerNetworkObject in clientPlayerClones)
{
// When the server is not the host this needs to be done
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
{
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
}
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(networkManager.LocalClientId, playerNetworkObject);
}
}
ClientNetworkManagerPostStartInit();
// Notification that at this time the server and client(s) are instantiated,
// started, and connected on both sides.
@@ -409,11 +504,9 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
protected void DeRegisterSceneManagerHandler()
{
if (NetcodeIntegrationTestHelpers.ClientSceneHandler != null)
{
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsLoad -= ClientSceneHandler_CanClientsLoad;
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsUnload -= ClientSceneHandler_CanClientsUnload;
}
IntegrationTestSceneHandler.CanClientsLoad -= ClientSceneHandler_CanClientsLoad;
IntegrationTestSceneHandler.CanClientsUnload -= ClientSceneHandler_CanClientsUnload;
IntegrationTestSceneHandler.NetworkManagers.Clear();
}
/// <summary>
@@ -423,11 +516,9 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
protected void RegisterSceneManagerHandler()
{
if (NetcodeIntegrationTestHelpers.ClientSceneHandler != null)
{
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsLoad += ClientSceneHandler_CanClientsLoad;
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsUnload += ClientSceneHandler_CanClientsUnload;
}
IntegrationTestSceneHandler.CanClientsLoad += ClientSceneHandler_CanClientsLoad;
IntegrationTestSceneHandler.CanClientsUnload += ClientSceneHandler_CanClientsUnload;
NetcodeIntegrationTestHelpers.RegisterSceneManagerHandler(m_ServerNetworkManager, true);
}
private bool ClientSceneHandler_CanClientsUnload()
@@ -440,6 +531,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
return CanClientsLoad();
}
protected bool OnCanSceneCleanUpUnload(Scene scene)
{
return true;
}
/// <summary>
/// This shuts down all NetworkManager instances registered via the
/// <see cref="NetcodeIntegrationTestHelpers"/> class and cleans up
@@ -452,11 +548,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
// Shutdown and clean up both of our NetworkManager instances
try
{
if (NetcodeIntegrationTestHelpers.ClientSceneHandler != null)
{
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsLoad -= ClientSceneHandler_CanClientsLoad;
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsUnload -= ClientSceneHandler_CanClientsUnload;
}
DeRegisterSceneManagerHandler();
NetcodeIntegrationTestHelpers.Destroy();
@@ -476,6 +568,8 @@ namespace Unity.Netcode.TestHelpers.Runtime
// Cleanup any remaining NetworkObjects
DestroySceneNetworkObjects();
UnloadRemainingScenes();
// reset the m_ServerWaitForTick for the next test to initialize
s_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate);
VerboseDebug($"Exiting {nameof(ShutdownAndCleanUp)}");
@@ -517,6 +611,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
[OneTimeTearDown]
public void OneTimeTearDown()
{
IntegrationTestSceneHandler.VerboseDebugMode = false;
VerboseDebug($"Entering {nameof(OneTimeTearDown)}");
OnOneTimeTearDown();
@@ -528,7 +623,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
// Disable NetcodeIntegrationTest auto-label feature
NetcodeIntegrationTestHelpers.RegisterNetcodeIntegrationTest(false);
UnloadRemainingScenes();
VerboseDebug($"Exiting {nameof(OneTimeTearDown)}");
IsRunning = false;
}
/// <summary>
@@ -783,5 +882,32 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
m_UseHost = hostOrServer == HostOrServer.Host ? true : false;
}
/// <summary>
/// Just a helper function to avoid having to write the entire assert just to check if you
/// timed out.
/// </summary>
protected void AssertOnTimeout(string timeOutErrorMessage, TimeoutHelper assignedTimeoutHelper = null)
{
var timeoutHelper = assignedTimeoutHelper != null ? assignedTimeoutHelper : s_GlobalTimeoutHelper;
Assert.False(timeoutHelper.TimedOut, timeOutErrorMessage);
}
private void UnloadRemainingScenes()
{
// Unload any remaining scenes loaded but the test runner scene
// Note: Some tests only unload the server-side instance, and this
// just assures no currently loaded scenes will impact the next test
for (int i = 0; i < SceneManager.sceneCount; i++)
{
var scene = SceneManager.GetSceneAt(i);
if (!scene.IsValid() || !scene.isLoaded || scene.name.Contains(NetcodeIntegrationTestHelpers.FirstPartOfTestRunnerSceneName) || !OnCanSceneCleanUpUnload(scene))
{
continue;
}
VerboseDebug($"Unloading scene {scene.name}-{scene.handle}");
var asyncOperation = SceneManager.UnloadSceneAsync(scene);
}
}
}
}

View File

@@ -16,7 +16,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
public static class NetcodeIntegrationTestHelpers
{
public const int DefaultMinFrames = 1;
public const float DefaultTimeout = 1f;
public const float DefaultTimeout = 4f;
private static List<NetworkManager> s_NetworkManagerInstances = new List<NetworkManager>();
private static Dictionary<NetworkManager, MultiInstanceHooks> s_Hooks = new Dictionary<NetworkManager, MultiInstanceHooks>();
private static bool s_IsStarted;
@@ -118,25 +118,23 @@ namespace Unity.Netcode.TestHelpers.Runtime
}
}
private const string k_FirstPartOfTestRunnerSceneName = "InitTestScene";
internal const string FirstPartOfTestRunnerSceneName = "InitTestScene";
public static List<NetworkManager> NetworkManagerInstances => s_NetworkManagerInstances;
internal static IntegrationTestSceneHandler ClientSceneHandler = null;
internal static List<IntegrationTestSceneHandler> ClientSceneHandlers = new List<IntegrationTestSceneHandler>();
/// <summary>
/// Registers the IntegrationTestSceneHandler for integration tests.
/// The default client behavior is to not load scenes on the client side.
/// </summary>
private static void RegisterSceneManagerHandler(NetworkManager networkManager)
internal static void RegisterSceneManagerHandler(NetworkManager networkManager, bool allowServer = false)
{
if (!networkManager.IsServer)
if (!networkManager.IsServer || networkManager.IsServer && allowServer)
{
if (ClientSceneHandler == null)
{
ClientSceneHandler = new IntegrationTestSceneHandler();
}
networkManager.SceneManager.SceneManagerHandler = ClientSceneHandler;
var handler = new IntegrationTestSceneHandler(networkManager);
ClientSceneHandlers.Add(handler);
networkManager.SceneManager.SceneManagerHandler = handler;
}
}
@@ -148,11 +146,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
public static void CleanUpHandlers()
{
if (ClientSceneHandler != null)
foreach (var handler in ClientSceneHandlers)
{
ClientSceneHandler.Dispose();
ClientSceneHandler = null;
handler.Dispose();
}
ClientSceneHandlers.Clear();
}
/// <summary>
@@ -171,17 +169,10 @@ namespace Unity.Netcode.TestHelpers.Runtime
}
}
public static NetworkManager CreateServer()
private static void AddUnityTransport(NetworkManager networkManager)
{
// Create gameObject
var go = new GameObject("NetworkManager - Server");
// Create networkManager component
var server = go.AddComponent<NetworkManager>();
NetworkManagerInstances.Insert(0, server);
// Create transport
var unityTransport = go.AddComponent<UnityTransport>();
var unityTransport = networkManager.gameObject.AddComponent<UnityTransport>();
// We need to increase this buffer size for tests that spawn a bunch of things
unityTransport.MaxPayloadSize = 256000;
unityTransport.MaxSendQueueSize = 1024 * 1024;
@@ -191,11 +182,22 @@ namespace Unity.Netcode.TestHelpers.Runtime
unityTransport.ConnectTimeoutMS = 500;
// Set the NetworkConfig
server.NetworkConfig = new NetworkConfig()
networkManager.NetworkConfig = new NetworkConfig()
{
// Set transport
NetworkTransport = unityTransport
};
}
public static NetworkManager CreateServer()
{
// Create gameObject
var go = new GameObject("NetworkManager - Server");
// Create networkManager component
var server = go.AddComponent<NetworkManager>();
NetworkManagerInstances.Insert(0, server);
AddUnityTransport(server);
return server;
}
@@ -229,6 +231,17 @@ namespace Unity.Netcode.TestHelpers.Runtime
return true;
}
internal static NetworkManager CreateNewClient(int identifier)
{
// Create gameObject
var go = new GameObject("NetworkManager - Client - " + identifier);
// Create networkManager component
var networkManager = go.AddComponent<NetworkManager>();
AddUnityTransport(networkManager);
return networkManager;
}
/// <summary>
/// Used to add a client to the already existing list of clients
/// </summary>
@@ -237,23 +250,10 @@ namespace Unity.Netcode.TestHelpers.Runtime
public static bool CreateNewClients(int clientCount, out NetworkManager[] clients)
{
clients = new NetworkManager[clientCount];
var activeSceneName = SceneManager.GetActiveScene().name;
for (int i = 0; i < clientCount; i++)
{
// Create gameObject
var go = new GameObject("NetworkManager - Client - " + i);
// Create networkManager component
clients[i] = go.AddComponent<NetworkManager>();
// Create transport
var unityTransport = go.AddComponent<UnityTransport>();
// Set the NetworkConfig
clients[i].NetworkConfig = new NetworkConfig()
{
// Set transport
NetworkTransport = unityTransport
};
clients[i] = CreateNewClient(i);
}
NetworkManagerInstances.AddRange(clients);
@@ -264,12 +264,32 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// Stops one single client and makes sure to cleanup any static variables in this helper
/// </summary>
/// <param name="clientToStop"></param>
public static void StopOneClient(NetworkManager clientToStop)
public static void StopOneClient(NetworkManager clientToStop, bool destroy = true)
{
clientToStop.Shutdown();
s_Hooks.Remove(clientToStop);
Object.Destroy(clientToStop.gameObject);
NetworkManagerInstances.Remove(clientToStop);
if (destroy)
{
Object.Destroy(clientToStop.gameObject);
NetworkManagerInstances.Remove(clientToStop);
}
}
/// <summary>
/// Starts one single client and makes sure to register the required hooks and handlers
/// </summary>
/// <param name="clientToStart"></param>
public static void StartOneClient(NetworkManager clientToStart)
{
clientToStart.StartClient();
s_Hooks[clientToStart] = new MultiInstanceHooks();
clientToStart.MessagingSystem.Hook(s_Hooks[clientToStart]);
if (!NetworkManagerInstances.Contains(clientToStart))
{
NetworkManagerInstances.Add(clientToStart);
}
// if set, then invoke this for the client
RegisterHandlers(clientToStart);
}
/// <summary>
@@ -315,7 +335,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
private static bool VerifySceneIsValidForClientsToLoad(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode)
{
// exclude test runner scene
if (sceneName.StartsWith(k_FirstPartOfTestRunnerSceneName))
if (sceneName.StartsWith(FirstPartOfTestRunnerSceneName))
{
return false;
}
@@ -342,7 +362,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
// warning about using the currently active scene
var scene = SceneManager.GetActiveScene();
// As long as this is a test runner scene (or most likely a test runner scene)
if (scene.name.StartsWith(k_FirstPartOfTestRunnerSceneName))
if (scene.name.StartsWith(FirstPartOfTestRunnerSceneName))
{
// Register the test runner scene just so we avoid another warning about not being able to find the
// scene to synchronize NetworkObjects. Next, add the currently active test runner scene to the scenes
@@ -458,8 +478,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
// this feature only works with NetcodeIntegrationTest derived classes
if (IsNetcodeIntegrationTestRunning)
{
// Add the object identifier component
networkObject.gameObject.AddComponent<ObjectNameIdentifier>();
if (networkObject.GetComponent<ObjectNameIdentifier>() == null && networkObject.GetComponentInChildren<ObjectNameIdentifier>() == null)
{
// Add the object identifier component
networkObject.gameObject.AddComponent<ObjectNameIdentifier>();
}
}
}
@@ -722,7 +745,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
/// <param name="result">The result. If null, it will fail if the predicate is not met</param>
/// <param name="timeout">The max time in seconds to wait for</param>
internal static IEnumerator WaitForMessageOfTypeReceived<T>(NetworkManager toBeReceivedBy, ResultWrapper<bool> result = null, float timeout = 0.5f) where T : INetworkMessage
internal static IEnumerator WaitForMessageOfTypeReceived<T>(NetworkManager toBeReceivedBy, ResultWrapper<bool> result = null, float timeout = DefaultTimeout) where T : INetworkMessage
{
var hooks = s_Hooks[toBeReceivedBy];
var check = new MessageReceiveCheckWithResult { CheckType = typeof(T) };
@@ -750,7 +773,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
/// <param name="result">The result. If null, it will fail if the predicate is not met</param>
/// <param name="timeout">The max time in seconds to wait for</param>
internal static IEnumerator WaitForMessageOfTypeHandled<T>(NetworkManager toBeReceivedBy, ResultWrapper<bool> result = null, float timeout = 0.5f) where T : INetworkMessage
internal static IEnumerator WaitForMessageOfTypeHandled<T>(NetworkManager toBeReceivedBy, ResultWrapper<bool> result = null, float timeout = DefaultTimeout) where T : INetworkMessage
{
var hooks = s_Hooks[toBeReceivedBy];
if (!hooks.HandleChecks.ContainsKey(typeof(T)))