-
Notifications
You must be signed in to change notification settings - Fork 459
test: Added MultiInstanceHelper for multi-instance runtime tests #817
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
306 changes: 306 additions & 0 deletions
306
com.unity.multiplayer.mlapi/Tests/Runtime/MultiInstance/MultiInstanceHelpers.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,306 @@ | ||
| using System; | ||
| using System.Collections; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using MLAPI.Configuration; | ||
| using NUnit.Framework; | ||
| using UnityEngine; | ||
| using UnityEngine.SceneManagement; | ||
|
|
||
| namespace MLAPI.RuntimeTests | ||
| { | ||
| /// <summary> | ||
| /// Provides helpers for running multi instance tests. | ||
| /// </summary> | ||
| internal static class MultiInstanceHelpers | ||
| { | ||
| /// <summary> | ||
| /// Creates NetworkingManagers and configures them for use in a multi instance setting. | ||
| /// </summary> | ||
| /// <param name="clientCount">The amount of clients</param> | ||
| /// <param name="server">The server NetworkManager</param> | ||
| /// <param name="clients">The clients NetworkManagers</param> | ||
| public static bool Create(int clientCount, out NetworkManager server, out NetworkManager[] clients) | ||
| { | ||
| clients = new NetworkManager[clientCount]; | ||
|
|
||
| for (int i = 0; i < clientCount; i++) | ||
| { | ||
| // Create gameObject | ||
| var go = new GameObject("NetworkManager - Client - " + i); | ||
|
|
||
| // Create networkManager component | ||
| clients[i] = go.AddComponent<NetworkManager>(); | ||
|
|
||
| // Set config | ||
| clients[i].NetworkConfig = new NetworkConfig() | ||
| { | ||
| // Set the current scene to prevent unexpected log messages which would trigger a failure | ||
| RegisteredScenes = new List<string>() { SceneManager.GetActiveScene().name }, | ||
| // Set transport | ||
| NetworkTransport = go.AddComponent<SIPTransport>() | ||
| }; | ||
| } | ||
|
|
||
| { | ||
| // Create gameObject | ||
| var go = new GameObject("NetworkManager - Server"); | ||
|
|
||
| // Create networkManager component | ||
| server = go.AddComponent<NetworkManager>(); | ||
|
|
||
| // Set config | ||
| server.NetworkConfig = new NetworkConfig() | ||
| { | ||
| // Set the current scene to prevent unexpected log messages which would trigger a failure | ||
| RegisteredScenes = new List<string>() { SceneManager.GetActiveScene().name }, | ||
| // Set transport | ||
| NetworkTransport = go.AddComponent<SIPTransport>() | ||
| }; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Starts NetworkManager instances created by the Create method. | ||
| /// </summary> | ||
| /// <param name="host">Whether or not to create a Host instead of Server</param> | ||
| /// <param name="server">The Server NetworkManager</param> | ||
| /// <param name="clients">The Clients NetworkManager</param> | ||
| public static bool Start(bool host, NetworkManager server, NetworkManager[] clients) | ||
| { | ||
| if (host) | ||
| { | ||
| server.StartHost(); | ||
| } | ||
| else | ||
| { | ||
| server.StartClient(); | ||
| } | ||
|
|
||
| for (int i = 0; i < clients.Length; i++) | ||
| { | ||
| clients[i].StartClient(); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| // Empty MonoBehaviour that is a holder of coroutine | ||
| private class CoroutineRunner : MonoBehaviour | ||
| { | ||
| } | ||
|
|
||
| private static CoroutineRunner s_CoroutineRunner; | ||
|
|
||
| /// <summary> | ||
| /// Runs a IEnumerator as a Coroutine on a dummy GameObject. | ||
| /// </summary> | ||
| /// <param name="enumerator">The IEnumerator to run</param> | ||
| public static Coroutine Run(IEnumerator enumerator) | ||
| { | ||
| if (s_CoroutineRunner == null) | ||
| { | ||
| s_CoroutineRunner = new GameObject(nameof(CoroutineRunner)).AddComponent<CoroutineRunner>(); | ||
| } | ||
|
|
||
| return s_CoroutineRunner.StartCoroutine(enumerator); | ||
| } | ||
|
|
||
| public class CoroutineResultWrapper<T> | ||
| { | ||
| public T Result; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Normally we would only allow player prefabs to be set to a prefab. Not runtime created objects. | ||
| /// In order to prevent having a Resource folder full of a TON of prefabs that we have to maintain, | ||
| /// MultiInstanceHelper has a helper function that lets you mark a runtime created object to be | ||
| /// treated as a prefab by the MLAPI. That's how we can get away with creating the player prefab | ||
| /// at runtime without it being treated as a SceneObject or causing other conflicts with the MLAPI. | ||
| /// </summary> | ||
| /// <param name="networkObject">The networkObject to be treated as Prefab</param> | ||
| /// <param name="globalObjectIdHash">The GlobalObjectId to force</param> | ||
| public static void MakeNetworkedObjectTestPrefab(NetworkObject networkObject, uint globalObjectIdHash = default) | ||
| { | ||
| // Set a globalObjectId for prefab | ||
| if (globalObjectIdHash != default) | ||
| { | ||
| networkObject.TempGlobalObjectIdHashOverride = globalObjectIdHash; | ||
| } | ||
|
|
||
| // Force generation | ||
| networkObject.GenerateGlobalObjectIdHash(); | ||
|
|
||
| // Prevent object from being snapped up as a scene object | ||
| networkObject.IsSceneObject = false; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Waits on the client side to be connected. | ||
| /// </summary> | ||
| /// <param name="client">The client</param> | ||
| /// <param name="result">The result. If null, it will automatically assert</param> | ||
| /// <param name="maxFrames">The max frames to wait for</param> | ||
| public static IEnumerator WaitForClientConnected(NetworkManager client, CoroutineResultWrapper<bool> result = null, int maxFrames = 64) | ||
| { | ||
| if (client.IsServer) | ||
| { | ||
| throw new InvalidOperationException("Cannot wait for connected as server"); | ||
| } | ||
|
|
||
| int startFrame = Time.frameCount; | ||
|
|
||
| while (Time.frameCount - startFrame <= maxFrames && !client.IsConnectedClient) | ||
| { | ||
| int nextFrameId = Time.frameCount + 1; | ||
| yield return new WaitUntil(() => Time.frameCount >= nextFrameId); | ||
| } | ||
|
|
||
| bool res = client.IsConnectedClient; | ||
|
|
||
| if (result != null) | ||
| { | ||
| result.Result = res; | ||
| } | ||
| else | ||
| { | ||
| Assert.True(res, "Client never connected"); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Waits on the server side for 1 client to be connected | ||
| /// </summary> | ||
| /// <param name="server">The server</param> | ||
| /// <param name="result">The result. If null, it will automatically assert</param> | ||
| /// <param name="maxFrames">The max frames to wait for</param> | ||
| public static IEnumerator WaitForClientConnectedToServer(NetworkManager server, CoroutineResultWrapper<bool> result = null, int maxFrames = 64) | ||
| { | ||
| if (!server.IsServer) | ||
| { | ||
| throw new InvalidOperationException("Cannot wait for connected as client"); | ||
| } | ||
|
|
||
| int startFrame = Time.frameCount; | ||
|
|
||
| while (Time.frameCount - startFrame <= maxFrames && server.ConnectedClients.Count != (server.IsHost ? 2 : 1)) | ||
| { | ||
| int nextFrameId = Time.frameCount + 1; | ||
| yield return new WaitUntil(() => Time.frameCount >= nextFrameId); | ||
| } | ||
|
|
||
| bool res = server.ConnectedClients.Count == (server.IsHost ? 2 : 1); | ||
|
|
||
| if (result != null) | ||
| { | ||
| result.Result = res; | ||
| } | ||
| else | ||
| { | ||
| Assert.True(res, "Client never connected to server"); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets a NetworkObject instance as it's represented by a certain peer. | ||
| /// </summary> | ||
| /// <param name="networkObjectId">The networkObjectId to get</param> | ||
| /// <param name="representation">The representation to get the object from</param> | ||
| /// <param name="result">The result</param> | ||
| /// <param name="failIfNull">Whether or not to fail if no object is found and result is null</param> | ||
| /// <param name="maxFrames">The max frames to wait for</param> | ||
| public static IEnumerator GetNetworkObjectByRepresentation(ulong networkObjectId, NetworkManager representation, CoroutineResultWrapper<NetworkObject> result, bool failIfNull = true, int maxFrames = 64) | ||
| { | ||
| if (result == null) | ||
| { | ||
| throw new ArgumentNullException("Result cannot be null"); | ||
| } | ||
|
|
||
| int startFrame = Time.frameCount; | ||
|
|
||
| while (Time.frameCount - startFrame <= maxFrames && representation.SpawnManager.SpawnedObjects.All(x => x.Value.NetworkObjectId != networkObjectId)) | ||
| { | ||
| int nextFrameId = Time.frameCount + 1; | ||
| yield return new WaitUntil(() => Time.frameCount >= nextFrameId); | ||
| } | ||
|
|
||
| result.Result = representation.SpawnManager.SpawnedObjects.First(x => x.Value.NetworkObjectId == networkObjectId).Value; | ||
|
|
||
| if (failIfNull && result.Result == null) | ||
| { | ||
| Assert.Fail("NetworkObject could not be found"); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets a NetworkObject instance as it's represented by a certain peer. | ||
| /// </summary> | ||
| /// <param name="predicate">The predicate used to filter for your target NetworkObject</param> | ||
| /// <param name="representation">The representation to get the object from</param> | ||
| /// <param name="result">The result</param> | ||
| /// <param name="failIfNull">Whether or not to fail if no object is found and result is null</param> | ||
| /// <param name="maxFrames">The max frames to wait for</param> | ||
| public static IEnumerator GetNetworkObjectByRepresentation(Func<NetworkObject, bool> predicate, NetworkManager representation, CoroutineResultWrapper<NetworkObject> result, bool failIfNull = true, int maxFrames = 64) | ||
|
NoelStephensUnity marked this conversation as resolved.
|
||
| { | ||
| if (result == null) | ||
| { | ||
| throw new ArgumentNullException("Result cannot be null"); | ||
| } | ||
|
|
||
| if (predicate == null) | ||
| { | ||
| throw new ArgumentNullException("Predicate cannot be null"); | ||
| } | ||
|
|
||
| int startFrame = Time.frameCount; | ||
|
|
||
| while (Time.frameCount - startFrame <= maxFrames && !representation.SpawnManager.SpawnedObjects.Any(x => predicate(x.Value))) | ||
| { | ||
| int nextFrameId = Time.frameCount + 1; | ||
| yield return new WaitUntil(() => Time.frameCount >= nextFrameId); | ||
| } | ||
|
|
||
| result.Result = representation.SpawnManager.SpawnedObjects.FirstOrDefault(x => predicate(x.Value)).Value; | ||
|
|
||
| if (failIfNull && result.Result == null) | ||
| { | ||
| Assert.Fail("NetworkObject could not be found"); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Waits for a predicate condition to be met | ||
| /// </summary> | ||
| /// <param name="predicate">The predicate to wait for</param> | ||
| /// <param name="result">The result. If null, it will fail if the predicate is not met</param> | ||
| /// <param name="maxFrames">The max frames to wait for</param> | ||
| public static IEnumerator WaitForCondition(Func<bool> predicate, CoroutineResultWrapper<bool> result = null, int maxFrames = 64) | ||
|
NoelStephensUnity marked this conversation as resolved.
|
||
| { | ||
| if (predicate == null) | ||
| { | ||
| throw new ArgumentNullException("Predicate cannot be null"); | ||
| } | ||
|
|
||
| int startFrame = Time.frameCount; | ||
|
|
||
| while (Time.frameCount - startFrame <= maxFrames && !predicate()) | ||
| { | ||
| int nextFrameId = Time.frameCount + 1; | ||
| yield return new WaitUntil(() => Time.frameCount >= nextFrameId); | ||
| } | ||
|
|
||
| bool res = predicate(); | ||
|
|
||
| if (result != null) | ||
| { | ||
| result.Result = res; | ||
| } | ||
| else | ||
| { | ||
| Assert.True(res, "PREDICATE CONDITION"); | ||
| } | ||
| } | ||
| } | ||
| } | ||
3 changes: 3 additions & 0 deletions
3
com.unity.multiplayer.mlapi/Tests/Runtime/MultiInstance/MultiInstanceHelpers.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.