|
| 1 | +using System.Collections; |
| 2 | +using System.Text; |
| 3 | +using NUnit.Framework; |
| 4 | +using Unity.Netcode; |
| 5 | +using Unity.Netcode.TestHelpers.Runtime; |
| 6 | +using UnityEngine.SceneManagement; |
| 7 | +using UnityEngine.TestTools; |
| 8 | + |
| 9 | +namespace TestProject.RuntimeTests |
| 10 | +{ |
| 11 | + /// <summary> |
| 12 | + /// Validates that a client promoted to session owner registers the scenes it loaded outside of |
| 13 | + /// <see cref="NetworkSceneManager"/> so it can synchronize newly joining clients. |
| 14 | + /// Runs against the DAHost by default and against the live CMB service when USE_CMB_SERVICE is set. |
| 15 | + /// </summary> |
| 16 | + internal class SessionOwnerPromotionSceneTests : NetcodeIntegrationTest |
| 17 | + { |
| 18 | + private const string k_LocallyLoadedScene = "EmptyScene"; |
| 19 | + protected override int NumberOfClients => 2; |
| 20 | + |
| 21 | + private Scene m_OriginalActiveScene; |
| 22 | + private Scene m_LocallyLoadedScene; |
| 23 | + |
| 24 | + public SessionOwnerPromotionSceneTests() : base(HostOrServer.DAHost) { } |
| 25 | + |
| 26 | + protected override void OnOneTimeSetup() |
| 27 | + { |
| 28 | + m_OriginalActiveScene = SceneManager.GetActiveScene(); |
| 29 | + base.OnOneTimeSetup(); |
| 30 | + } |
| 31 | + |
| 32 | + protected override IEnumerator OnTearDown() |
| 33 | + { |
| 34 | + SceneManager.SetActiveScene(m_OriginalActiveScene); |
| 35 | + if (m_LocallyLoadedScene.IsValid() && m_LocallyLoadedScene.isLoaded) |
| 36 | + { |
| 37 | + var scene = m_LocallyLoadedScene; |
| 38 | + SceneManager.UnloadSceneAsync(scene); |
| 39 | + yield return WaitForConditionOrTimeOut(() => !scene.isLoaded); |
| 40 | + } |
| 41 | + yield return base.OnTearDown(); |
| 42 | + } |
| 43 | + |
| 44 | + private NetworkManager m_InitialSessionOwner; |
| 45 | + |
| 46 | + /// <summary> |
| 47 | + /// Avoids <see cref="NetcodeIntegrationTest.GetAuthorityNetworkManager"/> since it fails the test when |
| 48 | + /// invoked before a new session owner has been promoted. |
| 49 | + /// </summary> |
| 50 | + private bool IsNewSessionOwnerPromoted(StringBuilder errorLog) |
| 51 | + { |
| 52 | + // Have to do our own loop looking for a session owner as GetAuthorityNetworkManager will fail the test if no session owner is found. |
| 53 | + // It is expected that no session owner will be found for a frame or two during session owner promotion. |
| 54 | + NetworkManager sessionOwner = null; |
| 55 | + foreach (var networkManager in m_NetworkManagers) |
| 56 | + { |
| 57 | + if (networkManager.LocalClient.IsSessionOwner) |
| 58 | + { |
| 59 | + sessionOwner = networkManager; |
| 60 | + break; |
| 61 | + } |
| 62 | + } |
| 63 | + |
| 64 | + if (sessionOwner == null || sessionOwner == m_InitialSessionOwner) |
| 65 | + { |
| 66 | + errorLog.AppendLine("No new session owner has been promoted."); |
| 67 | + return false; |
| 68 | + } |
| 69 | + |
| 70 | + var allClientsSynchronized = true; |
| 71 | + foreach (var networkManager in m_NetworkManagers) |
| 72 | + { |
| 73 | + if (networkManager.CurrentSessionOwner != sessionOwner.LocalClientId) |
| 74 | + { |
| 75 | + errorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Incorrect {nameof(NetworkManager.CurrentSessionOwner)}! Expected: {sessionOwner.LocalClientId}. Has: {networkManager.CurrentSessionOwner}."); |
| 76 | + allClientsSynchronized = false; |
| 77 | + } |
| 78 | + } |
| 79 | + return allClientsSynchronized; |
| 80 | + } |
| 81 | + |
| 82 | + /// <summary> |
| 83 | + /// Loads and activates a scene without using <see cref="NetworkSceneManager"/>, promotes a new session owner, |
| 84 | + /// and then verifies the promoted session owner can synchronize late joining clients. |
| 85 | + /// </summary> |
| 86 | + [UnityTest] |
| 87 | + public IEnumerator PromotedSessionOwnerSynchronizesLocallyLoadedActiveScene() |
| 88 | + { |
| 89 | + // Load and activate a scene outside of NetworkSceneManager after the session has started, |
| 90 | + // so no NetworkManager instance has a network scene handle registered for it. |
| 91 | + var loadOperation = SceneManager.LoadSceneAsync(k_LocallyLoadedScene, LoadSceneMode.Additive); |
| 92 | + Assert.NotNull(loadOperation, "Failed to load test scene!"); |
| 93 | + |
| 94 | + yield return WaitForConditionOrTimeOut(() => loadOperation.isDone); |
| 95 | + AssertOnTimeout($"Timed out waiting for {k_LocallyLoadedScene} to load!"); |
| 96 | + |
| 97 | + // Set the newly loaded scene as the active scene |
| 98 | + // Now all clients have this unregistered scene as their active scene. |
| 99 | + m_LocallyLoadedScene = SceneManager.GetSceneByName(k_LocallyLoadedScene); |
| 100 | + SceneManager.SetActiveScene(m_LocallyLoadedScene); |
| 101 | + |
| 102 | + // Now all clients have this unregistered scene as their active scene. |
| 103 | + m_InitialSessionOwner = GetAuthorityNetworkManager(); |
| 104 | + |
| 105 | + // Sanity check: Double-check the newly loaded scene is unregistered on all clients |
| 106 | + foreach (var networkManager in m_NetworkManagers) |
| 107 | + { |
| 108 | + var isSceneRegistered = networkManager.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(m_LocallyLoadedScene.handle); |
| 109 | + Assert.That(isSceneRegistered, Is.False, $"[Client-{networkManager.LocalClientId}] Scene loaded by the UnityEngine SceneManager should not have been registered!"); |
| 110 | + } |
| 111 | + |
| 112 | + if (m_UseCmbService) |
| 113 | + { |
| 114 | + // the CMB service promotes a client when the current session owner disconnects |
| 115 | + yield return StopOneClient(m_InitialSessionOwner); |
| 116 | + } |
| 117 | + else |
| 118 | + { |
| 119 | + // The DAHost can manually promote another client as the session owner. |
| 120 | + m_InitialSessionOwner.PromoteSessionOwner(GetNonAuthorityNetworkManager().LocalClientId); |
| 121 | + } |
| 122 | + |
| 123 | + yield return WaitForConditionOrTimeOut(IsNewSessionOwnerPromoted); |
| 124 | + AssertOnTimeout("Session owner promotion failed!"); |
| 125 | + |
| 126 | + // Connect a late-joining client to verify the new session owner handles the previously unregistered scene. |
| 127 | + yield return CreateAndStartNewClient(); |
| 128 | + } |
| 129 | + } |
| 130 | +} |
0 commit comments