using System;
using System.Collections.Generic;
using UnityEngine;
using NUnit.Framework;
using Unity.Netcode.Transports.UTP;
namespace Unity.Netcode.TestHelpers.Runtime
{
///
/// Helper class to instantiate a NetworkManager
/// This also provides the ability to:
/// --- instantiate GameObjects with NetworkObject components that returns a Guid for accessing it later.
/// --- add NetworkBehaviour components to the instantiated GameObjects
/// --- spawn a NetworkObject using its parent GameObject's Guid
/// Call StartNetworkManager in the constructor of your runtime unit test class.
/// Call ShutdownNetworkManager in the destructor of your runtime unit test class.
///
/// Includes a useful "BuffersMatch" method that allows you to compare two buffers (returns true if they match false if not)
///
public static class NetworkManagerHelper
{
public static NetworkManager NetworkManagerObject { get; internal set; }
public static GameObject NetworkManagerGameObject { get; internal set; }
public static Dictionary InstantiatedGameObjects = new Dictionary();
public static Dictionary InstantiatedNetworkObjects = new Dictionary();
public static NetworkManagerOperatingMode CurrentNetworkManagerMode;
///
/// This provides the ability to start NetworkManager in various modes
///
public enum NetworkManagerOperatingMode
{
None,
Host,
Server,
Client,
}
///
/// Called upon the RpcQueueTests being instantiated.
/// This creates an instance of the NetworkManager to be used during unit tests.
/// Currently, the best method to run unit tests is by starting in host mode as you can
/// send messages to yourself (i.e. Host-Client to Host-Server and vice versa).
/// As such, the default setting is to start in Host mode.
///
/// parameter to specify which mode you want to start the NetworkManager
/// parameter to specify custom NetworkConfig settings
/// true if it was instantiated or is already instantiate otherwise false means it failed to instantiate
public static bool StartNetworkManager(out NetworkManager networkManager, NetworkManagerOperatingMode managerMode = NetworkManagerOperatingMode.Host, NetworkConfig networkConfig = null)
{
// If we are changing the current manager mode and the current manager mode is not "None", then stop the NetworkManager mode
if (CurrentNetworkManagerMode != managerMode && CurrentNetworkManagerMode != NetworkManagerOperatingMode.None)
{
StopNetworkManagerMode();
}
if (NetworkManagerGameObject == null)
{
NetworkManagerGameObject = new GameObject(nameof(NetworkManager));
NetworkManagerObject = NetworkManagerGameObject.AddComponent();
if (NetworkManagerObject == null)
{
networkManager = null;
return false;
}
Debug.Log($"{nameof(NetworkManager)} Instantiated.");
var unityTransport = NetworkManagerGameObject.AddComponent();
if (networkConfig == null)
{
networkConfig = new NetworkConfig
{
EnableSceneManagement = false,
};
}
NetworkManagerObject.NetworkConfig = networkConfig;
NetworkManagerObject.NetworkConfig.NetworkTransport = unityTransport;
// Starts the network manager in the mode specified
StartNetworkManagerMode(managerMode);
}
networkManager = NetworkManagerObject;
return true;
}
///
/// Add a GameObject with a NetworkObject component
///
/// the name of the object
///
public static Guid AddGameNetworkObject(string nameOfGameObject)
{
var gameObjectId = Guid.NewGuid();
// Create the player object that we will spawn as a host
var gameObject = new GameObject(nameOfGameObject);
Assert.IsNotNull(gameObject);
var networkObject = gameObject.AddComponent();
Assert.IsNotNull(networkObject);
Assert.IsFalse(InstantiatedGameObjects.ContainsKey(gameObjectId));
Assert.IsFalse(InstantiatedNetworkObjects.ContainsKey(gameObjectId));
InstantiatedGameObjects.Add(gameObjectId, gameObject);
InstantiatedNetworkObjects.Add(gameObjectId, networkObject);
return gameObjectId;
}
///
/// Helper class to add a component to the GameObject with a NetoworkObject component
///
/// NetworkBehaviour component being added to the GameObject
/// ID returned to reference the game object
///
public static T AddComponentToObject(Guid gameObjectIdentifier) where T : NetworkBehaviour
{
Assert.IsTrue(InstantiatedGameObjects.ContainsKey(gameObjectIdentifier));
return InstantiatedGameObjects[gameObjectIdentifier].AddComponent();
}
///
/// Spawn the NetworkObject, so Rpcs can flow
///
/// ID returned to reference the game object
public static void SpawnNetworkObject(Guid gameObjectIdentifier)
{
Assert.IsTrue(InstantiatedNetworkObjects.ContainsKey(gameObjectIdentifier));
if (!InstantiatedNetworkObjects[gameObjectIdentifier].IsSpawned)
{
InstantiatedNetworkObjects[gameObjectIdentifier].Spawn();
}
}
///
/// Starts the NetworkManager in the current mode specified by managerMode
///
/// the mode to start the NetworkManager as
private static void StartNetworkManagerMode(NetworkManagerOperatingMode managerMode)
{
CurrentNetworkManagerMode = managerMode;
switch (CurrentNetworkManagerMode)
{
case NetworkManagerOperatingMode.Host:
{
// Starts the host
NetworkManagerObject.StartHost();
break;
}
case NetworkManagerOperatingMode.Server:
{
// Starts the server
NetworkManagerObject.StartServer();
break;
}
case NetworkManagerOperatingMode.Client:
{
// Starts the client
NetworkManagerObject.StartClient();
break;
}
}
// If we started an netcode session
if (CurrentNetworkManagerMode != NetworkManagerOperatingMode.None)
{
// With some unit tests the Singleton can still be from a previous unit test
// depending upon the order of operations that occurred.
if (NetworkManager.Singleton != NetworkManagerObject)
{
NetworkManagerObject.SetSingleton();
}
// Only log this if we started an netcode session
Debug.Log($"{CurrentNetworkManagerMode} started.");
}
}
///
/// Stops the current mode of the NetworkManager
///
private static void StopNetworkManagerMode()
{
NetworkManagerObject.Shutdown();
Debug.Log($"{CurrentNetworkManagerMode} stopped.");
CurrentNetworkManagerMode = NetworkManagerOperatingMode.None;
}
// This is called, even if we assert and exit early from a test
public static void ShutdownNetworkManager()
{
// clean up any game objects created with custom unit testing components
foreach (var entry in InstantiatedGameObjects)
{
UnityEngine.Object.DestroyImmediate(entry.Value);
}
InstantiatedGameObjects.Clear();
if (NetworkManagerGameObject != null)
{
Debug.Log($"{nameof(NetworkManager)} shutdown.");
StopNetworkManagerMode();
UnityEngine.Object.DestroyImmediate(NetworkManagerGameObject);
Debug.Log($"{nameof(NetworkManager)} destroyed.");
}
NetworkManagerGameObject = null;
NetworkManagerObject = null;
}
public static bool BuffersMatch(int indexOffset, long targetSize, byte[] sourceArray, byte[] originalArray)
{
long largeInt64Blocks = targetSize >> 3; // Divide by 8
int originalArrayOffset = 0;
// process by 8 byte blocks if we can
for (long i = 0; i < largeInt64Blocks; i++)
{
if (BitConverter.ToInt64(sourceArray, indexOffset) != BitConverter.ToInt64(originalArray, originalArrayOffset))
{
return false;
}
indexOffset += 8;
originalArrayOffset += 8;
}
long offset = largeInt64Blocks * 8;
long remainder = targetSize - offset;
// 4 byte block
if (remainder >= 4)
{
if (BitConverter.ToInt32(sourceArray, indexOffset) != BitConverter.ToInt32(originalArray, originalArrayOffset))
{
return false;
}
indexOffset += 4;
originalArrayOffset += 4;
offset += 4;
}
// Remainder of bytes < 4
if (targetSize - offset > 0)
{
for (long i = 0; i < (targetSize - offset); i++)
{
if (sourceArray[indexOffset + i] != originalArray[originalArrayOffset + i])
{
return false;
}
}
}
return true;
}
}
}