From 6289abaa6570d4dc2ad2f7abded5664c41e9edb1 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 20 Jul 2026 12:26:42 -0500 Subject: [PATCH 1/7] fix - Issue where disabled in-scene placed NetworkObjects could not be spawned after starting a session. - Issue where users are allowed to spawn a NetworkObject with a GlobalObjectIdHash value of 0 (zero). --- .../Editor/InScenePlacedProcessor.cs | 14 +++ .../Runtime/Configuration/NetworkPrefabs.cs | 95 ++++++++++++++----- .../Runtime/Core/NetworkObject.cs | 29 +++++- .../Runtime/Spawning/NetworkSpawnManager.cs | 54 ++++++++++- 4 files changed, 158 insertions(+), 34 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs index f7529de12c..8f66c3aa4f 100644 --- a/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs +++ b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs @@ -24,6 +24,12 @@ public void OnProcessScene(Scene scene, BuildReport report) log.AddInfo(scene.name, scene.handle); foreach (var networkObject in FindObjects.FromSceneByType(scene, true)) { + // Trap for users just creating things during runtime where this will be zero. + if (networkObject.GlobalObjectIdHash == 0) + { + log.Warning(new Context(LogLevel.Developer, $"{nameof(NetworkObject)}'s GlobalObjectIdHash value is zero! Runtime creating of {nameof(NetworkObject)}s is not supported. Skipping processing.").AddNetworkObject(networkObject)); + continue; + } if (networkObject.SceneOrigin.IsValid() && networkObject.SceneOrigin.handle != scene.handle) { log.Warning(new Context(LogLevel.Developer, $"{nameof(NetworkObject)}'s SceneOrigin doesn't match current scene being processed! Skipping processing.").AddInfo("SceneOrigin", networkObject.SceneOriginHandle).AddNetworkObject(networkObject)); @@ -36,7 +42,15 @@ public void OnProcessScene(Scene scene, BuildReport report) continue; } + // If already marked, the do nothing. + if (networkObject.InScenePlaced) + { + continue; + } + networkObject.InScenePlaced = true; + // Will not be true when making a build and the values are serialized. + networkObject.InScenePlacedPostProcessorMarkedDuringRuntime = Application.isPlaying; } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs index 45d93ad9d7..499bbe74cc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs @@ -47,22 +47,81 @@ public class NetworkPrefabs [NonSerialized] private List m_Prefabs = new List(); + [NonSerialized] + private Dictionary m_PrefabHashIds = new Dictionary(); + [NonSerialized] private List m_RuntimeAddedPrefabs = new List(); - private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) + private bool InternalAddPrefab(NetworkPrefab networkPrefab) { if (AddPrefabRegistration(networkPrefab)) { // Don't add this to m_RuntimeAddedPrefabs // This prefab is now in the PrefabList, so if we shutdown and initialize again, we'll pick it up from there. m_Prefabs.Add(networkPrefab); + + // We are not getting all potential overrides but just determining if the prefab has been registered. + if (!m_PrefabHashIds.ContainsKey(networkPrefab.SourcePrefabGlobalObjectIdHash)) + { + m_PrefabHashIds.Add(networkPrefab.SourcePrefabGlobalObjectIdHash, networkPrefab); + } + if (!m_PrefabHashIds.ContainsKey(networkPrefab.TargetPrefabGlobalObjectIdHash)) + { + m_PrefabHashIds.Add(networkPrefab.TargetPrefabGlobalObjectIdHash, networkPrefab); + } + return true; } + return false; } - private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) + private void InternalRemovePrefab(NetworkPrefab networkPrefab) { m_Prefabs.Remove(networkPrefab); + m_PrefabHashIds.Remove(networkPrefab.SourcePrefabGlobalObjectIdHash); + } + + internal bool IsBasedOnRegisteredPrefab(NetworkObject networkObject) + { + + + return m_PrefabHashIds.ContainsKey(networkObject.GlobalObjectIdHash); + } + + internal bool IsActualPrefabAsset(NetworkObject networkObject) + { + var isActualPrefabAsset = false; + if (m_PrefabHashIds.TryGetValue(networkObject.GlobalObjectIdHash, out NetworkPrefab networkPrefab)) + { + switch(networkPrefab.Override) + { + case NetworkPrefabOverride.Prefab: + case NetworkPrefabOverride.None: + { + isActualPrefabAsset = networkPrefab.Prefab != null && networkObject.gameObject == networkPrefab.Prefab; + break; + } + case NetworkPrefabOverride.Hash: + { + isActualPrefabAsset = networkPrefab.SourceHashToOverride == networkObject.GlobalObjectIdHash; + break; + } + } + } + return isActualPrefabAsset; + } + + private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) + { + // Don't add this to m_RuntimeAddedPrefabs + // This prefab is now in the PrefabList, so if we shutdown and initialize again, we'll pick it up from there. + InternalAddPrefab(networkPrefab); + // Log warning if this returns false? + } + + private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) + { + InternalRemovePrefab(networkPrefab); } /// @@ -93,6 +152,7 @@ internal void Shutdown() /// When true, logs warnings about invalid prefabs that are removed during initialization public void Initialize(bool warnInvalid = true) { + m_PrefabHashIds.Clear(); m_Prefabs.Clear(); NetworkPrefabsLists.RemoveAll(x => x == null); foreach (var list in NetworkPrefabsLists) @@ -113,7 +173,7 @@ public void Initialize(bool warnInvalid = true) prefabs.AddRange(list.PrefabList); } } - + m_PrefabHashIds = new Dictionary(); m_Prefabs = new List(); List removeList = null; @@ -124,11 +184,7 @@ public void Initialize(bool warnInvalid = true) foreach (var networkPrefab in prefabs) { - if (AddPrefabRegistration(networkPrefab)) - { - m_Prefabs.Add(networkPrefab); - } - else + if (!InternalAddPrefab(networkPrefab)) { removeList?.Add(networkPrefab); } @@ -136,11 +192,7 @@ public void Initialize(bool warnInvalid = true) foreach (var networkPrefab in m_RuntimeAddedPrefabs) { - if (AddPrefabRegistration(networkPrefab)) - { - m_Prefabs.Add(networkPrefab); - } - else + if (!InternalAddPrefab(networkPrefab)) { removeList?.Add(networkPrefab); } @@ -171,14 +223,12 @@ public void Initialize(bool warnInvalid = true) /// public bool Add(NetworkPrefab networkPrefab) { - if (AddPrefabRegistration(networkPrefab)) + var added = InternalAddPrefab(networkPrefab); + if (added) { - m_Prefabs.Add(networkPrefab); m_RuntimeAddedPrefabs.Add(networkPrefab); - return true; } - - return false; + return added; } /// @@ -197,8 +247,7 @@ public void Remove(NetworkPrefab prefab) { throw new ArgumentNullException(nameof(prefab)); } - - m_Prefabs.Remove(prefab); + InternalRemovePrefab(prefab); m_RuntimeAddedPrefabs.Remove(prefab); OverrideToNetworkPrefab.Remove(prefab.TargetPrefabGlobalObjectIdHash); NetworkPrefabOverrideLinks.Remove(prefab.SourcePrefabGlobalObjectIdHash); @@ -294,14 +343,12 @@ private bool AddPrefabRegistration(NetworkPrefab networkPrefab) uint source = networkPrefab.SourcePrefabGlobalObjectIdHash; uint target = networkPrefab.TargetPrefabGlobalObjectIdHash; - // Make sure the prefab isn't already registered. if (NetworkPrefabOverrideLinks.ContainsKey(source)) { - var networkObject = networkPrefab.Prefab.GetComponent(); - + var nameOrHashOverride = networkPrefab.Override == NetworkPrefabOverride.Hash ? $"Hash: {networkPrefab.SourcePrefabGlobalObjectIdHash}" : networkPrefab.Prefab?.name; // This should never happen, but in the case it somehow does log an error and remove the duplicate entry - Debug.LogError($"{nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!"); + Debug.LogError($"{nameof(NetworkPrefab)} ({nameOrHashOverride}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!"); return false; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 52e4dad4f1..264f2b510d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1258,6 +1258,13 @@ internal set } } + /// + /// This provides a means to determine if the post processing had applied + /// the in-scene placed status or if it was already serialized. This is used + /// when determining if the thing being spawned is a valid thing to spawn. + /// + internal bool InScenePlacedPostProcessorMarkedDuringRuntime; + /// /// Sets whether this NetworkObject was instantiated as part of a scene /// @@ -1877,6 +1884,13 @@ private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool play } } + // Trap for runtime generated instances as this is not valid + if (GlobalObjectIdHash == 0) + { + NetworkManager.Log.ErrorServer(new Context(LogLevel.Error, $"{name} has a {nameof(GlobalObjectIdHash)} value of {GlobalObjectIdHash}(zero)!" + + $"This is typically a sign of runtime generated {nameof(NetworkObject)}s which is not supported.")); + return; + } // Calculate the legacy IsSceneObject value as the public field is obsolete with warning // We can't break the public behavior of the field. @@ -1884,11 +1898,13 @@ private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool play var legacyIsSceneObject = IsSceneObject.HasValue && IsSceneObject.Value; #pragma warning restore CS0618 // Type or member is obsolete - // If SpawnInternal is being called on an object that is marked as InScenePlaced, - // The scene object was never automatically spawned when the scene was loaded. - // Count this object as a dynamically spawned object. - // TODO-[MTT-15388]: Actually support disabled/not spawned InScenePlaced NetworkObjects - if (InScenePlaced && !HasBeenSpawned) + // If the initial state of the GameObject was disabled and InScenePlaced is marked, + // then spawn it as in-scene placed.[MTT-15388] + // Otherwise: + // If we are marked as in-scene place, have never been spawned, and the root GameObject + // was not disabled upon being instantiated, then treat this as a dynamically spawned + // instance. + if (InScenePlaced && !m_GameObjectWasDisabledWhenInstantiated && !HasBeenSpawned) { if (NetworkManagerOwner.NetworkConfig.EnableSceneManagement && NetworkManagerOwner.LogLevel <= LogLevel.Developer) { @@ -3701,10 +3717,13 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) } } + private bool m_GameObjectWasDisabledWhenInstantiated; + private void Awake() { SetCachedParent(transform.parent); SceneOrigin = gameObject.scene; + m_GameObjectWasDisabledWhenInstantiated = gameObject.activeInHierarchy; } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 74d0ee121e..0e869d67c1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1121,7 +1121,9 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n NetworkLog.LogError(new Context(LogLevel.Developer, "Player prefab is marked as belonging to a scene. This may cause issues.").AddNetworkObject(networkObject).AddInfo("SceneName", networkObject.SceneOrigin.name)); networkObject.InScenePlaced = false; } - NetworkLog.InternalAssert(sceneObject == networkObject.InScenePlaced, "Legacy sceneObject value should match calculated InScenePlaced value."); + // This logic is no longer true with the adjustments to spawn pre-disabled in-scene placed NetworkObjects. + // Leaving this for reference purposes. + //NetworkLog.InternalAssert(sceneObject == networkObject.InScenePlaced, "Legacy sceneObject value should match calculated InScenePlaced value."); if (!networkObject.InScenePlaced && NetworkManager.LogLevel <= LogLevel.Error) { @@ -1580,10 +1582,52 @@ internal void ServerSpawnSceneObjectsOnStartSweep() continue; } - // This used to be two loops. - // The first added all NetworkObjects to a list and the second spawned all NetworkObjects in the list. - // Now, a parent will set its children's IsSceneObject value when spawned, so we check for null or for true. - if (networkObject.InScenePlaced) + // Do not attempt to spawn if it is the actual prefab asset itself: + // - This is not supported by NGO. + // - This will lead to other issues if it gets destroyed, when de-spawned, but the prefab is still registered. + // - This also prevents from spawning integration test prefabs. + if (NetworkManager.NetworkConfig.Prefabs.IsActualPrefabAsset(networkObject)) + { + NetworkManager.Log.Warning(new Context(LogLevel.Developer, $"Skipping {networkObject.name} as it is the actual prefab asset itself!")); + continue; + } + + // Determine if this is even a valid thing to spawn: + // - If it is not based on a registered prefab, it is invalid. + // - If the GlobalObjectIdHash is zero, it is invalid. + var isInvalidInstanceToSpawn = !NetworkManager.NetworkConfig.Prefabs.IsBasedOnRegisteredPrefab(networkObject) || networkObject.GlobalObjectIdHash == 0; + + // If we are a valid prefab asset, marked as in-scene placed, but this was marked during runtime by the post processor. + if (!isInvalidInstanceToSpawn && networkObject.InScenePlaced && networkObject.InScenePlacedPostProcessorMarkedDuringRuntime) + { + // Then it is not in-scene placed and was pre-instantiated. Spawn dynamically. + networkObject.InScenePlaced = false; + } + else if(networkObject.InScenePlaced && !networkObject.InScenePlacedPostProcessorMarkedDuringRuntime) + { + // If this was marked as in-scene placed within the editor, then it is valid. + isInvalidInstanceToSpawn = false; + } + + var wasPreInstantiated = !networkObject.IsSpawned && !networkObject.InScenePlaced; + + // Dynamically created NetworkObjects instances are not supported and will not be spawned during the sweep. + if (wasPreInstantiated && isInvalidInstanceToSpawn) + { + // If this isn't the original prefab asset being skipped over (integration test would be a good example), then log the error. + if (!NetworkManager.NetworkConfig.Prefabs.IsActualPrefabAsset(networkObject)) + { + NetworkManager.Log.Error(new Context(LogLevel.Error, $"{networkObject.name} appears to be a pre-instantiated {nameof(GameObject)} " + + $"with a {nameof(NetworkObject)} component instance that is not a registered prefab nor is it an in-scene placed {nameof(NetworkObject)}." + + $" Dynamically creating unregistered {nameof(NetworkObject)}s is not supported! {networkObject.name} will not be spawned.")); + } + continue; + } + + // The only valid things to spawn during the sweep are: + // - In-scene placed NetworkObjects. + // - Pre-instantiated NetworkObjects that are registered with the NetworkManager's network prefab list(s). + if (networkObject.InScenePlaced || wasPreInstantiated) { var ownerId = networkObject.OwnerClientId; if (NetworkManager.DistributedAuthorityMode) From e70d82a8377edf2a20a0be13186592bdd752e559 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 20 Jul 2026 12:27:21 -0500 Subject: [PATCH 2/7] update NetworkAnimator not pointing to an animator within awake now just logs a warning as opposed to an error. --- .../Runtime/Components/NetworkAnimator.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index c999c26e9f..673960e121 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -768,7 +768,10 @@ protected virtual void Awake() if (!m_Animator) { #if !UNITY_EDITOR - Debug.LogError($"{nameof(NetworkAnimator)} {name} does not have an {nameof(UnityEngine.Animator)} assigned to it. The {nameof(NetworkAnimator)} will not initialize properly."); + if (!m_Animator) + { + Debug.LogWarning($"{nameof(NetworkAnimator)} {name} does not have an {nameof(UnityEngine.Animator)} assigned to it. The {nameof(NetworkAnimator)} will not initialize properly."); + } #endif return; } From 876826337cb90a26124e867a04a361954499edbc Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 20 Jul 2026 12:28:44 -0500 Subject: [PATCH 3/7] test Updating tests based on the fixes applied to this branch. A large portion is related to some integration tests trying to spawn NetworkObjects with a GlobalObjectIdHash value of zero(0). --- .../Runtime/Connection/ConnectionApproval.cs | 5 + .../Runtime/NetworkBehaviourGenericTests.cs | 42 +- .../Runtime/NetworkManagerPlayerPrefab.cs | 34 +- .../Runtime/Prefabs/AddNetworkPrefabTests.cs | 49 +- .../Prefabs/NetworkPrefabHandlerTests.cs | 40 +- .../NetworkBehaviourReferenceTests.cs | 435 +++++++++++++----- .../TestHelpers/NetcodeIntegrationTest.cs | 11 +- .../Runtime/Animation/NetworkAnimatorTests.cs | 14 +- 8 files changed, 454 insertions(+), 176 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs index 2e0c81b690..56231899d4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs @@ -40,6 +40,11 @@ public ConnectionApprovalTests(PlayerCreation playerCreation) private string m_ValidationToken; + internal override bool ShouldCreatePlayerPrefab() + { + return m_PlayerCreation != PlayerCreation.NoPlayer && m_PlayerCreation != PlayerCreation.FailValidation; + } + protected override bool ShouldCheckForSpawnedPlayers() { return m_PlayerCreation != PlayerCreation.NoPlayer; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs index 5569278aa0..967e29e0fc 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs @@ -39,6 +39,7 @@ public override void OnNetworkDespawn() protected override void OnServerAndClientsCreated() { m_PrefabToSpawn = CreateNetworkObjectPrefab("TestPrefab"); + m_PrefabToSpawn.AddComponent(); var childObject = new GameObject { @@ -137,8 +138,6 @@ public IEnumerator ValidatedDisableddNetworkBehaviourWarning([Values] bool disab public IEnumerator ValidateNoSpam() { m_AllowServerToStart = true; - var objectToTest = new GameObject(); - var simpleNetworkBehaviour = objectToTest.AddComponent(); // Now just start the Host yield return StartServerAndClients(); @@ -146,23 +145,34 @@ public IEnumerator ValidateNoSpam() // set the log level to developer m_ServerNetworkManager.LogLevel = LogLevel.Developer; - // The only valid condition for this would be if the NetworkBehaviour is spawned. - simpleNetworkBehaviour.IsSpawned = true; + // We make an instance of the m_PrefabToSpawn + var validateInstance = Object.Instantiate(m_PrefabToSpawn); + // Then destroy the NetworkObject componwent of that instance. + Object.DestroyImmediate(validateInstance.GetComponent()); + // Now get the Networkbehaviour and verify when you attempt to get a reference + // to the NetworkObject a warning is logged. + var simpleNetworkBehaviour = validateInstance.GetComponent(); + simpleNetworkBehaviour.IsSpawned = true; + // Verify the warning gets logged under normal conditions var isNull = simpleNetworkBehaviour.NetworkObject == null; LogAssert.Expect(LogType.Warning, $"[Netcode] Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?"); - var networkObjectToTest = objectToTest.AddComponent(); - networkObjectToTest.NetworkManagerOwner = m_ServerNetworkManager; - networkObjectToTest.Spawn(); + simpleNetworkBehaviour.IsSpawned = false; + simpleNetworkBehaviour = null; + // Destroy this test instance + Object.DestroyImmediate(validateInstance); + + // Now create a spawned instance (NetworkObject will exist) + var instance = SpawnObject(m_PrefabToSpawn, GetAuthorityNetworkManager()).GetComponent(); // Assure no log messages are logged when they should not be logged - isNull = simpleNetworkBehaviour.NetworkObject != null; + isNull = instance.GetComponent().NetworkObject != null; LogAssert.NoUnexpectedReceived(); - networkObjectToTest.Despawn(); - Object.Destroy(networkObjectToTest); + instance.Despawn(); + Object.Destroy(instance.gameObject); } /// @@ -182,13 +192,11 @@ public IEnumerator ValidateDeleteChildNetworkBehaviour() // Now just start the Host yield return StartServerAndClients(); - var parentObject = new GameObject(); - var childObject = new GameObject(); - childObject.transform.parent = parentObject.transform; - var parentNetworkObject = parentObject.AddComponent(); - childObject.AddComponent(); - parentNetworkObject.Spawn(); + var serverInstance = SpawnObject(m_PrefabToSpawn, GetAuthorityNetworkManager()); + var parentNetworkObject = serverInstance.GetComponent(); + var childObject = parentNetworkObject.transform.GetChild(0).gameObject; + yield return s_DefaultWaitForTick; // Destroy the child object with child NetworkBehaviour @@ -201,7 +209,7 @@ public IEnumerator ValidateDeleteChildNetworkBehaviour() // Destroy the parent object which should not cause any exceptions // (validating the fix) - Object.Destroy(parentObject); + Object.Destroy(serverInstance); } protected override void OnPlayerPrefabGameObjectCreated() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerPlayerPrefab.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerPlayerPrefab.cs index 2eeb1dac6f..097e29399b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerPlayerPrefab.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerPlayerPrefab.cs @@ -20,23 +20,29 @@ public NetworkManagerPlayerPrefab(HostOrServer hostOrServer) : base(hostOrServer { } - /// - /// Assure no player prefab is assigned. - /// - protected override void OnServerAndClientsCreated() + internal override bool ShouldCreatePlayerPrefab() { - foreach (var networkManager in m_NetworkManagers) - { - networkManager.NetworkConfig.PlayerPrefab = null; - } - base.OnServerAndClientsCreated(); + return false; } - protected override void OnNewClientCreated(NetworkManager networkManager) - { - networkManager.NetworkConfig.PlayerPrefab = null; - base.OnNewClientCreated(networkManager); - } + ///// + ///// Assure no player prefab is assigned. + ///// + //protected override void OnServerAndClientsCreated() + //{ + // foreach (var networkManager in m_NetworkManagers) + // { + // networkManager.NetworkConfig.Prefabs.Remove(m_PlayerPrefab); + // networkManager.NetworkConfig.PlayerPrefab = null; + // } + // base.OnServerAndClientsCreated(); + //} + + //protected override void OnNewClientCreated(NetworkManager networkManager) + //{ + // networkManager.NetworkConfig.PlayerPrefab = null; + // base.OnNewClientCreated(networkManager); + //} /// /// Do not wait for spawned players as there are none. diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs index baab984b82..d0661a71fa 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs @@ -25,14 +25,12 @@ protected override IEnumerator OnSetup() yield return null; } - protected override void OnServerAndClientsCreated() + private GameObject GenerateAndRegisterPrefab() { - m_Prefab = new GameObject("Object"); - var networkObject = m_Prefab.AddComponent(); - m_Prefab.AddComponent(); - + var originalPrefabInstance = NetcodeIntegrationTestHelpers.CreateNetworkObject("PrefabTest"); // Make it a prefab - NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(originalPrefabInstance.GetComponent()); + m_ServerNetworkManager.NetworkConfig.SpawnTimeout = 0; m_ServerNetworkManager.NetworkConfig.ForceSamePrefabs = false; @@ -41,6 +39,12 @@ protected override void OnServerAndClientsCreated() client.NetworkConfig.SpawnTimeout = 0; client.NetworkConfig.ForceSamePrefabs = false; } + return originalPrefabInstance; + } + + protected override void OnServerAndClientsCreated() + { + RegisterPrefab(); } private EmptyComponent GetObjectForClient(ulong clientId) @@ -50,15 +54,25 @@ private EmptyComponent GetObjectForClient(ulong clientId) { if (component.IsSpawned && component.NetworkManager.LocalClientId == clientId) { - return component; + var prefabGlobalObjectIdHash = m_Prefab.GetComponent().GlobalObjectIdHash; + var componentGlobalObjectIdHash = m_Prefab.GetComponent().GlobalObjectIdHash; + if (prefabGlobalObjectIdHash == componentGlobalObjectIdHash) + { + return component; + } } } return null; } - private void RegisterPrefab() + private void RegisterPrefab(bool includeClients = true) { + m_Prefab = GenerateAndRegisterPrefab(); m_ServerNetworkManager.AddNetworkPrefab(m_Prefab); + if (!includeClients) + { + return; + } foreach (var client in m_ClientNetworkManagers) { client.AddNetworkPrefab(m_Prefab); @@ -89,7 +103,7 @@ public IEnumerator WhenSpawningBeforeAddingPrefab_SpawnFails() [UnityTest] public IEnumerator WhenSpawningAfterAddingServerPrefabButBeforeAddingClientPrefab_SpawnFails() { - m_ServerNetworkManager.AddNetworkPrefab(m_Prefab); + RegisterPrefab(false); var serverObject = Object.Instantiate(m_Prefab); serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; @@ -104,10 +118,12 @@ public IEnumerator WhenSpawningAfterAddingPrefabOnServerAndClient_SpawnSucceeds( RegisterPrefab(); var serverObject = Object.Instantiate(m_Prefab); - serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; - serverObject.GetComponent().Spawn(); - yield return NetcodeIntegrationTestHelpers.WaitForMessageOfTypeHandled(m_ClientNetworkManagers[0]); - Assert.IsNotNull(GetObjectForClient(m_ClientNetworkManagers[0].LocalClientId)); + var serverNetworkObject = serverObject.GetComponent(); + serverNetworkObject.NetworkManagerOwner = m_ServerNetworkManager; + serverNetworkObject.Spawn(); + yield return WaitForSpawnedOnAllOrTimeOut(serverObject); + AssertOnTimeout($"{serverObject.name} did not spawn on all clients!"); + Assert.IsTrue(m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.ContainsKey(serverNetworkObject.NetworkObjectId), $"Client did not spawn object!"); } [UnityTest] @@ -116,10 +132,13 @@ public IEnumerator WhenSpawningAfterRemovingPrefabOnClient_SpawnFails() RegisterPrefab(); var serverObject = Object.Instantiate(m_Prefab); + var serverNetworkObject = serverObject.GetComponent(); + serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; serverObject.GetComponent().Spawn(); - yield return NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(m_ClientNetworkManagers[0]); - Assert.IsNotNull(GetObjectForClient(m_ClientNetworkManagers[0].LocalClientId)); + yield return WaitForSpawnedOnAllOrTimeOut(serverObject); + AssertOnTimeout($"{serverObject.name} did not spawn on all clients!"); + Assert.IsTrue(m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.ContainsKey(serverNetworkObject.NetworkObjectId), $"Client did not spawn object!"); serverObject.GetComponent().Despawn(); yield return NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(m_ClientNetworkManagers[0]); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs index f964271a28..1e4f907180 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs @@ -28,11 +28,22 @@ public void OneTimeSetup() private uint m_ObjectId = 1; private GameObject MakeValidNetworkPrefab() { - Guid baseObjectID = NetworkManagerHelper.AddGameNetworkObject(k_TestPrefabObjectName + m_ObjectId.ToString()); - NetworkObject validPrefab = NetworkManagerHelper.InstantiatedNetworkObjects[baseObjectID]; - NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(validPrefab); + return GenerateAndRegisterPrefab(); + //Guid baseObjectID = NetworkManagerHelper.AddGameNetworkObject(k_TestPrefabObjectName + m_ObjectId.ToString()); + //NetworkObject validPrefab = NetworkManagerHelper.InstantiatedNetworkObjects[baseObjectID]; + //NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(validPrefab); + //m_ObjectId++; + //return validPrefab.gameObject; + } + + private GameObject GenerateAndRegisterPrefab() + { + // We must migrate this into the DDOL to avoid being + var originalPrefabInstance = NetcodeIntegrationTestHelpers.CreateNetworkObject(k_TestPrefabObjectName + m_ObjectId.ToString()); + // Make it a prefab + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(originalPrefabInstance.GetComponent()); m_ObjectId++; - return validPrefab.gameObject; + return originalPrefabInstance; } @@ -63,14 +74,23 @@ public void NetworkConfigInvalidNetworkPrefabTest() // Add a NetworkPrefab override with an invalid source prefab to override NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = null }); - // Add a NetworkPrefab override with a valid source prefab to override but an invalid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = MakeValidNetworkPrefab(), OverridingTargetPrefab = null }); - // Add a valid prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = MakeValidNetworkPrefab() }); + // Create a valid network prefab "asset". + var validPrefabAsset = MakeValidNetworkPrefab().GetComponent(); + // Add a NetworkPrefab override with a valid source prefab to override but an invalid target prefab. + NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = validPrefabAsset.gameObject, OverridingTargetPrefab = null }); + + // Now add the valid asset as a network prefab with no override. + NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = validPrefabAsset.gameObject }); + + var validPrefabForSourceHash = MakeValidNetworkPrefab().GetComponent(); + // This would be the scenario that a hash would be used (typically when scene management is disabled) + validPrefabForSourceHash.InScenePlaced = true; + // Add a NetworkPrefab override with a valid hash and valid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 11111111, OverridingTargetPrefab = MakeValidNetworkPrefab() }); + NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = validPrefabForSourceHash.GlobalObjectIdHash, OverridingTargetPrefab = validPrefabAsset.gameObject }); + // Add a NetworkPrefab override with a valid prefab and valid target prefab NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = MakeValidNetworkPrefab(), OverridingTargetPrefab = MakeValidNetworkPrefab() }); @@ -88,7 +108,7 @@ public void NetworkConfigInvalidNetworkPrefabTest() Assert.False(exceptionOccurred); // In the end we should only have 3 valid registered network prefabs - Assert.True(NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Count == 3); + Assert.AreEqual(3, NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Count); } private const string k_PrefabObjectName = "NetworkPrefabHandlerTestObject"; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs index 51d2ae8e71..fc27d77ff2 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs @@ -1,9 +1,12 @@ using System; using System.Collections; +using System.Collections.Generic; +using System.Text; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; +using Object = UnityEngine.Object; namespace Unity.Netcode.RuntimeTests { @@ -13,180 +16,394 @@ namespace Unity.Netcode.RuntimeTests /// - Deserializing NetworkObjectReference to NetworkObject /// - Implicit operators of NetworkObjectReference /// - internal class NetworkBehaviourReferenceTests : IDisposable + [TestFixture(HostOrServer.DAHost)] + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class NetworkBehaviourReferenceTests : NetcodeIntegrationTest { - [OneTimeSetUp] - public void OneTimeSetup() + protected override int NumberOfClients => 1; + + private GameObject m_TestPrefab; + + private TestNetworkBehaviour m_AuthorityValidatingInstance; + private TestNetworkBehaviour m_BehaviourToUseAsReference; + + public NetworkBehaviourReferenceTests(HostOrServer hostOrServer) : base(hostOrServer) { - // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. - NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); } - private class TestNetworkBehaviour : NetworkBehaviour + protected override void OnServerAndClientsCreated() { - public static bool ReceivedRPC; - - public NetworkVariable TestVariable = new NetworkVariable(); - - public TestNetworkBehaviour RpcReceivedBehaviour; + m_TestPrefab = CreateNetworkObjectPrefab("TestBehaviour"); + m_TestPrefab.AddComponent(); + base.OnServerAndClientsCreated(); + } - [ServerRpc] - public void SendReferenceServerRpc(NetworkBehaviourReference value) + #region Tests using non-null NetworkBehaviours and RPCs + private bool RpcWasReceivedAndBehaviourValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_AuthorityValidatingInstance.NetworkObjectId; + var authorityReferenceId = m_BehaviourToUseAsReference.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + var referenceNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) { - RpcReceivedBehaviour = (TestNetworkBehaviour)value; - ReceivedRPC = true; + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject) + && networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityReferenceId, out referenceNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + var referenceBehaviour = referenceNetworkObject.GetComponent(); + + if (!validatingBehaviour.ReceivedRPC) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the RPC!"); + continue; + } + if (!validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} failed to acquire the reference!"); + continue; + } + if (referenceBehaviour != validatingBehaviour.RpcReceivedBehaviour) + { + var currentReferenceBehaviour = validatingBehaviour.RpcReceivedBehaviour != null ? validatingBehaviour.RpcReceivedBehaviour.name : "null"; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference {referenceBehaviour.name} but was {currentReferenceBehaviour}!"); + } + } + else + { + var wasNotSpawned = validatingNetworkObject == null ? m_AuthorityValidatingInstance.name : m_BehaviourToUseAsReference.name; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {wasNotSpawned}!"); + } + validatingNetworkObject = null; + referenceNetworkObject = null; } + + return stringBuilder.Length == 0; } + [UnityTest] public IEnumerator TestRpc() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); - - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); - - testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference(testNetworkBehaviour)); - - // wait for rpc completion - float t = 0; - while (testNetworkBehaviour.RpcReceivedBehaviour == null) + var authority = GetAuthorityNetworkManager(); + m_AuthorityValidatingInstance = SpawnObject(m_TestPrefab, authority).GetComponent(); + m_BehaviourToUseAsReference = SpawnObject(m_TestPrefab, authority).GetComponent(); + var objectsToSpawn = new List() { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } + m_AuthorityValidatingInstance.NetworkObject, + m_BehaviourToUseAsReference.NetworkObject + }; - yield return null; - } + // Spawn the instances + yield return WaitForSpawnedOnAllOrTimeOut(objectsToSpawn); + AssertOnTimeout($"[{nameof(TestSerializeNull)}] Not all clients spawned all instances of {m_AuthorityValidatingInstance.name} or {m_BehaviourToUseAsReference.name}"); - // validate - Assert.AreEqual(testNetworkBehaviour, testNetworkBehaviour.RpcReceivedBehaviour); + // Explicitly send the NetworkBehaviour as a reference + m_AuthorityValidatingInstance.SendReferenceRpc(new NetworkBehaviourReference(m_BehaviourToUseAsReference)); + + // Validated the reference + yield return WaitForConditionOrTimeOut(RpcWasReceivedAndBehaviourValidated); + AssertOnTimeout($"[{nameof(TestRpc)}] Failed to validate reference!"); } + [UnityTest] - public IEnumerator TestSerializeNull([Values] bool initializeWithNull) + public IEnumerator TestRpcImplicitNetworkBehaviour() { - TestNetworkBehaviour.ReceivedRPC = false; - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + var authority = GetAuthorityNetworkManager(); + m_AuthorityValidatingInstance = SpawnObject(m_TestPrefab, authority).GetComponent(); + m_BehaviourToUseAsReference = SpawnObject(m_TestPrefab, authority).GetComponent(); + var objectsToSpawn = new List() + { + m_AuthorityValidatingInstance.NetworkObject, + m_BehaviourToUseAsReference.NetworkObject + }; - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Spawn the instances + yield return WaitForSpawnedOnAllOrTimeOut(objectsToSpawn); + AssertOnTimeout($"[{nameof(TestSerializeNull)}] Not all clients spawned all instances of {m_AuthorityValidatingInstance.name} or {m_BehaviourToUseAsReference.name}"); - // If not initializing with null, then use the default constructor with no assigned NetworkBehaviour - if (!initializeWithNull) - { - testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference()); - } - else // Otherwise, initialize and pass in null as the reference - { - testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference(null)); - } + // Implicitly send the NetworkBehaviour as a reference + m_AuthorityValidatingInstance.SendReferenceRpc(m_BehaviourToUseAsReference); + + // Validated the reference + yield return WaitForConditionOrTimeOut(RpcWasReceivedAndBehaviourValidated); + AssertOnTimeout($"[{nameof(TestRpc)}] Failed to validate reference!"); + } + #endregion - // wait for rpc completion - float t = 0; - while (!TestNetworkBehaviour.ReceivedRPC) + #region Tests using non-null NetworkBehaviours and NetworkVariable + private bool NetworkVariableChangedAndBehaviourValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_AuthorityValidatingInstance.NetworkObjectId; + var authorityReferenceId = m_BehaviourToUseAsReference.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + var referenceNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) { - t += Time.deltaTime; - if (t > 5f) + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject) + && networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityReferenceId, out referenceNetworkObject)) { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); + var validatingBehaviour = validatingNetworkObject.GetComponent(); + var referenceBehaviour = referenceNetworkObject.GetComponent(); + + if (!validatingBehaviour.TestVariableChanged) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the NetworkVariable update!"); + continue; + } + if (!validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} failed to acquire a reference!"); + continue; + } + if (referenceBehaviour != validatingBehaviour.TestVariableBehaviour) + { + var currentReferenceBehaviour = validatingBehaviour.TestVariableBehaviour != null ? validatingBehaviour.RpcReceivedBehaviour.name : "null"; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference {referenceBehaviour.name} but was {currentReferenceBehaviour}!"); + } } - - yield return null; + else + { + var wasNotSpawned = validatingNetworkObject == null ? m_AuthorityValidatingInstance.name : m_BehaviourToUseAsReference.name; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {wasNotSpawned}!"); + } + validatingNetworkObject = null; + referenceNetworkObject = null; } - - // validate - Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedBehaviour); + return stringBuilder.Length == 0; } + [UnityTest] - public IEnumerator TestRpcImplicitNetworkBehaviour() + public IEnumerator TestNetworkVariable() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + var authority = GetAuthorityNetworkManager(); + m_AuthorityValidatingInstance = SpawnObject(m_TestPrefab, authority).GetComponent(); + m_BehaviourToUseAsReference = SpawnObject(m_TestPrefab, authority).GetComponent(); + var objectsToSpawn = new List() + { + m_AuthorityValidatingInstance.NetworkObject, + m_BehaviourToUseAsReference.NetworkObject + }; - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Spawn the instances + yield return WaitForSpawnedOnAllOrTimeOut(objectsToSpawn); + AssertOnTimeout($"[{nameof(TestSerializeNull)}] Not all clients spawned all instances of {m_AuthorityValidatingInstance.name} or {m_BehaviourToUseAsReference.name}"); - testNetworkBehaviour.SendReferenceServerRpc(testNetworkBehaviour); + // Assure the authority instance's value is the default (null) value + Assert.IsNull((NetworkBehaviour)m_AuthorityValidatingInstance.TestVariable.Value); - // wait for rpc completion - float t = 0; - while (testNetworkBehaviour.RpcReceivedBehaviour == null) + // Implicitly assign the NetworkBehaviourReference by assigning the NetworkBehaviour to the NetworkVariable. + m_AuthorityValidatingInstance.TestVariable.Value = m_BehaviourToUseAsReference; + + // Validated the NetworkVariable reference propogates to clients + yield return WaitForConditionOrTimeOut(NetworkVariableChangedAndBehaviourValidated); + AssertOnTimeout($"[{nameof(TestRpc)}] Failed to validate reference!"); + } + #endregion + + #region Validating using NULL as a NetworkBehaviourReference + private bool RpcSerializingNullValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_AuthorityValidatingInstance.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) { - t += Time.deltaTime; - if (t > 5f) + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); + var validatingBehaviour = validatingNetworkObject.GetComponent(); + + if (!validatingBehaviour.ReceivedRPC) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the RPC!"); + continue; + } + if (validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} Acquired a reference when it should not have!"); + continue; + } + if (validatingBehaviour.RpcReceivedBehaviour != null) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference NULL but was {validatingBehaviour.RpcReceivedBehaviour.name}!"); + } } - - yield return null; + else + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {m_AuthorityValidatingInstance.name}!"); + } + validatingNetworkObject = null; } + return stringBuilder.Length == 0; + } - // validate - Assert.AreEqual(testNetworkBehaviour, testNetworkBehaviour.RpcReceivedBehaviour); + private bool NetworkVariableSerializingNullValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_AuthorityValidatingInstance.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + + if (!validatingBehaviour.TestVariableChanged) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not detected a change in the NetworkVariable!"); + continue; + } + if (validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} Acquired a reference when it should not have!"); + continue; + } + if (validatingBehaviour.TestVariableBehaviour != null) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference NULL but was {validatingBehaviour.TestVariableBehaviour.name}!"); + } + } + else + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {m_AuthorityValidatingInstance.name}!"); + } + validatingNetworkObject = null; + } + return stringBuilder.Length == 0; } - [Test] - public void TestNetworkVariable() + + [UnityTest] + public IEnumerator TestSerializeNull() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + var authority = GetAuthorityNetworkManager(); + m_AuthorityValidatingInstance = SpawnObject(m_TestPrefab, authority).GetComponent(); + yield return WaitForSpawnedOnAllOrTimeOut(m_AuthorityValidatingInstance.gameObject); + AssertOnTimeout($"[{nameof(TestSerializeNull)}] Not all clients spawned {m_AuthorityValidatingInstance.name}"); + // Initialize with NULL parameter + var initializeWithNull = new NetworkBehaviourReference(null); + var initializeWithNothing = new NetworkBehaviourReference(); + + m_AuthorityValidatingInstance.SendReferenceRpc(initializeWithNull); + yield return WaitForConditionOrTimeOut(RpcSerializingNullValidated); + AssertOnTimeout($"[{nameof(TestSerializeNull)}][Initialize with null parameter] Failed to validate null {nameof(NetworkBehaviour)} reference!"); + + // Reset the RPC NetworkBehaviourReference to the local instance for all spawned instances. + foreach (var networkManager in m_NetworkManagers) + { + var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_AuthorityValidatingInstance.NetworkObjectId].GetComponent(); + testBehaviour.RpcReceivedBehaviour = testBehaviour; + } - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Initialize with no parameter + m_AuthorityValidatingInstance.SendReferenceRpc(initializeWithNothing); + yield return WaitForConditionOrTimeOut(RpcSerializingNullValidated); + AssertOnTimeout($"[{nameof(TestSerializeNull)}][Initialize with no parameter] Failed to validate null {nameof(NetworkBehaviour)} reference!"); - // check default value is null - Assert.IsNull((NetworkBehaviour)testNetworkBehaviour.TestVariable.Value); + m_AuthorityValidatingInstance.TestVariable.Value = initializeWithNull; + yield return WaitForConditionOrTimeOut(NetworkVariableSerializingNullValidated); + AssertOnTimeout($"[{nameof(TestSerializeNull)}][Initialize with null parameter] Failed to validate null {nameof(NetworkBehaviour)} reference!"); - testNetworkBehaviour.TestVariable.Value = testNetworkBehaviour; + // Reset the NetworkVaraible NetworkBehaviourReference to the local instance for all spawned instances. + foreach (var networkManager in m_NetworkManagers) + { + var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_AuthorityValidatingInstance.NetworkObjectId].GetComponent(); + testBehaviour.TestVariableBehaviour = testBehaviour; + testBehaviour.TestVariableChanged = false; + } - Assert.AreEqual((NetworkBehaviour)testNetworkBehaviour.TestVariable.Value, testNetworkBehaviour); + // Initialize with no parameter + m_AuthorityValidatingInstance.TestVariable.Value = initializeWithNothing; + yield return WaitForConditionOrTimeOut(NetworkVariableSerializingNullValidated); + AssertOnTimeout($"[{nameof(TestSerializeNull)}][Initialize with no parameter] Failed to validate null {nameof(NetworkBehaviour)} reference!"); } + #endregion - [Test] - public void FailSerializeNonSpawnedNetworkObject() + #region Serialization Failure validation tests + + /// + /// This test is ok to create but not spawn. + /// + [UnityTest] + public IEnumerator FailSerializeNonSpawnedNetworkObject() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var component = networkObjectContext.Object.gameObject.AddComponent(); + yield return s_DefaultWaitForTick; + var instance = Object.Instantiate(m_TestPrefab); Assert.Throws(() => { - NetworkBehaviourReference outReference = component; + NetworkBehaviourReference outReference = instance.GetComponent(); }); + + Object.Destroy(instance); } - [Test] - public void FailSerializeGameObjectWithoutNetworkObject() + [UnityTest] + public IEnumerator FailSerializeGameObjectWithoutNetworkObject() { - using var gameObjectContext = UnityObjectContext.CreateGameObject(); - var component = gameObjectContext.Object.gameObject.AddComponent(); + yield return s_DefaultWaitForTick; + var instance = Object.Instantiate(m_TestPrefab); + Object.Destroy(instance.GetComponent()); Assert.Throws(() => { - NetworkBehaviourReference outReference = component; + NetworkBehaviourReference outReference = instance.GetComponent(); }); - } - public void Dispose() - { - //Stop, shutdown, and destroy - NetworkManagerHelper.ShutdownNetworkManager(); + Object.Destroy(instance); } - public NetworkBehaviourReferenceTests() + #endregion + + #region TestNetworkBehaviour component script + + private class TestNetworkBehaviour : NetworkBehaviour { - //Create, instantiate, and host - NetworkManagerHelper.StartNetworkManager(out _); + public bool ReceivedRPC; + public bool TestVariableChanged; + public bool AcquiredReference; + + public NetworkVariable TestVariable = new NetworkVariable(); + + public TestNetworkBehaviour TestVariableBehaviour; + + public TestNetworkBehaviour RpcReceivedBehaviour; + + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + // Set it to ourself so we can validate serializing null too. + RpcReceivedBehaviour = this; + TestVariableBehaviour = null; + base.OnNetworkPreSpawn(ref networkManager); + } + + public override void OnNetworkSpawn() + { + TestVariable.OnValueChanged += OnTestVariableChanged; + base.OnNetworkSpawn(); + } + + public override void OnNetworkPreDespawn() + { + TestVariable.OnValueChanged -= OnTestVariableChanged; + base.OnNetworkPreDespawn(); + } + + private void OnTestVariableChanged(NetworkBehaviourReference previous, NetworkBehaviourReference current) + { + TestVariableChanged = true; + AcquiredReference = current.TryGet(out TestVariableBehaviour, NetworkManager); + } + + [Rpc(SendTo.Everyone)] + public void SendReferenceRpc(NetworkBehaviourReference value) + { + AcquiredReference = value.TryGet(out RpcReceivedBehaviour, NetworkManager); + ReceivedRPC = true; + } } + #endregion } /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index 2a0c74258f..722b009912 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -777,6 +777,11 @@ protected void CreateServerAndClients() CreateServerAndClients(NumberOfClients); } + internal virtual bool ShouldCreatePlayerPrefab() + { + return true; + } + /// /// Creates the server and clients /// @@ -785,7 +790,11 @@ protected void CreateServerAndClients(int numberOfClients) { VerboseDebug($"Entering {nameof(CreateServerAndClients)}"); - CreatePlayerPrefab(); + if (ShouldCreatePlayerPrefab()) + { + CreatePlayerPrefab(); + } + if (m_EnableTimeTravel) { diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index d685fb68ca..46fd493b5c 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -356,6 +356,7 @@ public void ParameterBoundsCheck() { var gameObject = new GameObject(); gameObject.AddComponent(); + gameObject.AddComponent(); var networkAnimator = gameObject.AddComponent(); var writer = new FastBufferWriter(40, Unity.Collections.Allocator.TempJob); @@ -371,6 +372,8 @@ public void ParameterBoundsCheck() LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex($"parameters. Ignoring the remainger of this {nameof(ParametersUpdateMessage)}!")); // Pass in the invalid ParametersUpdateMessage networkAnimator.UpdateParameters(ref invalidParameters); + + Object.DestroyImmediate(gameObject); } private bool AllTriggersDetected(OwnerShipMode ownerShipMode) @@ -1078,27 +1081,18 @@ public void ShutdownWhileSpawnedAndStartBackUpTest() TimeTravelToNextTick(); - WaitForConditionOrTimeOutWithTimeTravel(() => !m_ServerNetworkManager.ShutdownInProgress); + WaitForConditionOrTimeOutWithTimeTravel(() => !m_ServerNetworkManager.ShutdownInProgress && m_ServerNetworkManager.IsConnectedClient); Assert.IsTrue(m_ServerTestHelperDespawned, $"Server-Side {nameof(AnimatorTestHelper)} did not have a valid IsServer setting!"); AssertOnTimeout($"Timed out waiting for the server to shutdown!"); VerboseDebug($" ++++++++++++++++++ Disconnect-Reconnect Restarting Server and Client ++++++++++++++++++ "); - // Since the dynamically generated PlayerPrefab is destroyed when the server shuts down, - // we need to create a new one and assign it to NetworkPrefab index 0 - m_PlayerPrefab = new GameObject("Player"); - NetworkObject networkObject = m_PlayerPrefab.AddComponent(); - NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); - m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs[playerPrefabIndex].Prefab = m_PlayerPrefab; - m_ServerNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; // Now, restart the server and the client m_ServerNetworkManager.StartHost(); foreach (var clientNetworkManager in m_ClientNetworkManagers) { - clientNetworkManager.NetworkConfig.Prefabs.Prefabs[playerPrefabIndex].Prefab = m_PlayerPrefab; - clientNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; clientNetworkManager.StartClient(); } From 9ec5725f7fd11864144834372a82dfe455d9b99e Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 21 Jul 2026 13:01:31 -0500 Subject: [PATCH 4/7] update Style and providing internal write access to the prefabs list. --- .../Runtime/Configuration/NetworkPrefabs.cs | 4 +++- .../Runtime/Spawning/NetworkSpawnManager.cs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs index 499bbe74cc..c25fe51e8a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs @@ -47,6 +47,8 @@ public class NetworkPrefabs [NonSerialized] private List m_Prefabs = new List(); + internal List InternalPrefabs => m_Prefabs; + [NonSerialized] private Dictionary m_PrefabHashIds = new Dictionary(); @@ -93,7 +95,7 @@ internal bool IsActualPrefabAsset(NetworkObject networkObject) var isActualPrefabAsset = false; if (m_PrefabHashIds.TryGetValue(networkObject.GlobalObjectIdHash, out NetworkPrefab networkPrefab)) { - switch(networkPrefab.Override) + switch (networkPrefab.Override) { case NetworkPrefabOverride.Prefab: case NetworkPrefabOverride.None: diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 0e869d67c1..5e846b3cd3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1603,13 +1603,13 @@ internal void ServerSpawnSceneObjectsOnStartSweep() // Then it is not in-scene placed and was pre-instantiated. Spawn dynamically. networkObject.InScenePlaced = false; } - else if(networkObject.InScenePlaced && !networkObject.InScenePlacedPostProcessorMarkedDuringRuntime) + else if (networkObject.InScenePlaced && !networkObject.InScenePlacedPostProcessorMarkedDuringRuntime) { // If this was marked as in-scene placed within the editor, then it is valid. isInvalidInstanceToSpawn = false; } - var wasPreInstantiated = !networkObject.IsSpawned && !networkObject.InScenePlaced; + var wasPreInstantiated = !networkObject.IsSpawned && !networkObject.InScenePlaced; // Dynamically created NetworkObjects instances are not supported and will not be spawned during the sweep. if (wasPreInstantiated && isInvalidInstanceToSpawn) From a27cffb90dc2c83817798fed20dcb5f34c73dd81 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 21 Jul 2026 13:02:27 -0500 Subject: [PATCH 5/7] test Potentially last iteration on fixing/updating integration tests. --- .../BufferDataValidationComponent.cs | 21 +- .../NetworkVariableTestComponent.cs | 96 ++-- .../NetworkVariable/NetworkVariableTests.cs | 50 +- ...etworkPrefabHandlerSynchronizationTests.cs | 15 +- .../Prefabs/NetworkPrefabHandlerTests.cs | 224 +++++---- .../NetworkPrefabHandlerWithDataTests.cs | 14 +- .../Profiling/NetworkVariableNameTests.cs | 48 +- .../Tests/Runtime/Rpc/RpcQueueTests.cs | 57 +-- .../Serialization/BaseReferenceTests.cs | 373 +++++++++++++++ .../Serialization/BaseReferenceTests.cs.meta | 2 + .../NetworkBehaviourReferenceTests.cs | 316 ++----------- .../NetworkObjectReferenceTests.cs | 443 ++++-------------- 12 files changed, 810 insertions(+), 849 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs index 3e46ec4b9e..d427251afe 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs @@ -38,8 +38,7 @@ internal class BufferDataValidationComponent : NetworkBehaviour private List m_SendBuffer; private List m_PreCalculatedBufferValues; - // Start is called before the first frame update - private void Start() + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) { m_WaitForValidation = false; m_CurrentBufferSize = BufferSizeStart; @@ -49,6 +48,7 @@ private void Start() { m_PreCalculatedBufferValues.Add((byte)Random.Range(0, 255)); } + base.OnNetworkPreSpawn(ref networkManager); } /// @@ -67,7 +67,12 @@ public bool IsTestComplete() // Update is called once per frame private void Update() { - if (NetworkManager.Singleton.IsListening && EnableTesting && !IsTestComplete() && !m_WaitForValidation) + if (!EnableTesting || !IsSpawned) + { + return; + } + + if (!m_WaitForValidation && !IsTestComplete()) { m_SendBuffer.Clear(); //Keep the current contents of the bufffer and fill the buffer with the delta difference of the buffer's current size and new size from the m_PreCalculatedBufferValues @@ -77,16 +82,16 @@ private void Update() m_WaitForValidation = true; //Send the buffer - SendBufferServerRpc(m_SendBuffer.ToArray()); + SendBufferRpc(m_SendBuffer.ToArray()); } + } /// - /// Server side RPC for testing + /// Sends to self for buffer queue testing /// - /// server rpc parameters - [ServerRpc] - private void SendBufferServerRpc(byte[] buffer) + [Rpc(SendTo.Me)] + private void SendBufferRpc(byte[] buffer) { TestFailed = !NetworkManagerHelper.BuffersMatch(0, buffer.Length, buffer, m_SendBuffer.ToArray()); if (!TestFailed) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs index 3e77d9762b..3db50bc595 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs @@ -242,7 +242,7 @@ internal class NetworkVariableTestComponent : NetworkBehaviour private float m_WaitForChangesTimeout; // Start is called before the first frame update - private void InitializeTest() + public void InitializeTest() { // Generic Constructor Test Coverage m_NetworkVariableBool = new NetworkVariable(); @@ -358,7 +358,7 @@ public bool IsTestComplete() public void Awake() { - InitializeTest(); + //InitializeTest(); } public void AssertAllValuesAreCorrect() @@ -423,56 +423,52 @@ public void AssertAllValuesAreCorrect() // Update is called once per frame private void Update() { - if (EnableTesting) + if (!EnableTesting || !IsSpawned) { - //Added timeout functionality for near future changes to NetworkVariables - if (!m_FinishedTests && m_ChangesAppliedToNetworkVariables) - { - //We finish testing if all NetworkVariables changed their value or we timed out waiting for - //all NetworkVariables to change their value - m_FinishedTests = DidAllValuesChange() || (m_WaitForChangesTimeout < Time.realtimeSinceStartup); - } - else + return; + } + + if (!m_ChangesAppliedToNetworkVariables) + { + //Now change all of the values to make sure we are at least testing the local callback + m_NetworkVariableBool.Value = false; + m_NetworkVariableByte.Value = 255; + m_NetworkVariableColor.Value = new Color(100, 100, 100); + m_NetworkVariableColor32.Value = new Color32(100, 100, 100, 100); + m_NetworkVariableDouble.Value = 1000; + m_NetworkVariableFloat.Value = 1000.0f; + m_NetworkVariableInt.Value = 1000; + m_NetworkVariableLong.Value = 100000; + m_NetworkVariableSByte.Value = -127; + m_NetworkVariableQuaternion.Value = new Quaternion(100, 100, 100, 100); + m_NetworkVariablePose.Value = new Pose(new Vector3(100, 100, 100), new Quaternion(100, 100, 100, 100)); + m_NetworkVariableShort.Value = short.MaxValue; + m_NetworkVariableVector4.Value = new Vector4(1000, 1000, 1000, 1000); + m_NetworkVariableVector3.Value = new Vector3(1000, 1000, 1000); + m_NetworkVariableVector2.Value = new Vector2(1000, 1000); + m_NetworkVariableRay.Value = new Ray(Vector3.one, Vector3.right); + m_NetworkVariableULong.Value = ulong.MaxValue; + m_NetworkVariableUInt.Value = uint.MaxValue; + m_NetworkVariableUShort.Value = ushort.MaxValue; + m_NetworkVariableFixedString32.Value = new FixedString32Bytes("FixedString32Bytes"); + m_NetworkVariableFixedString64.Value = new FixedString64Bytes("FixedString64Bytes"); + m_NetworkVariableFixedString128.Value = new FixedString128Bytes("FixedString128Bytes"); + m_NetworkVariableFixedString512.Value = new FixedString512Bytes("FixedString512Bytes"); + m_NetworkVariableFixedString4096.Value = new FixedString4096Bytes("FixedString4096Bytes"); + m_NetworkVariableManaged.Value = new ManagedNetworkSerializableType { - if (NetworkManager != null && NetworkManager.IsListening) - { - //Now change all of the values to make sure we are at least testing the local callback - m_NetworkVariableBool.Value = false; - m_NetworkVariableByte.Value = 255; - m_NetworkVariableColor.Value = new Color(100, 100, 100); - m_NetworkVariableColor32.Value = new Color32(100, 100, 100, 100); - m_NetworkVariableDouble.Value = 1000; - m_NetworkVariableFloat.Value = 1000.0f; - m_NetworkVariableInt.Value = 1000; - m_NetworkVariableLong.Value = 100000; - m_NetworkVariableSByte.Value = -127; - m_NetworkVariableQuaternion.Value = new Quaternion(100, 100, 100, 100); - m_NetworkVariablePose.Value = new Pose(new Vector3(100, 100, 100), new Quaternion(100, 100, 100, 100)); - m_NetworkVariableShort.Value = short.MaxValue; - m_NetworkVariableVector4.Value = new Vector4(1000, 1000, 1000, 1000); - m_NetworkVariableVector3.Value = new Vector3(1000, 1000, 1000); - m_NetworkVariableVector2.Value = new Vector2(1000, 1000); - m_NetworkVariableRay.Value = new Ray(Vector3.one, Vector3.right); - m_NetworkVariableULong.Value = ulong.MaxValue; - m_NetworkVariableUInt.Value = uint.MaxValue; - m_NetworkVariableUShort.Value = ushort.MaxValue; - m_NetworkVariableFixedString32.Value = new FixedString32Bytes("FixedString32Bytes"); - m_NetworkVariableFixedString64.Value = new FixedString64Bytes("FixedString64Bytes"); - m_NetworkVariableFixedString128.Value = new FixedString128Bytes("FixedString128Bytes"); - m_NetworkVariableFixedString512.Value = new FixedString512Bytes("FixedString512Bytes"); - m_NetworkVariableFixedString4096.Value = new FixedString4096Bytes("FixedString4096Bytes"); - m_NetworkVariableManaged.Value = new ManagedNetworkSerializableType - { - Str = "ManagedNetworkSerializableType", - Ints = new[] { 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000 }, - Embedded = new EmbeddedManagedNetworkSerializableType { Int = 20000 } - }; - - //Set the timeout (i.e. how long we will wait for all NetworkVariables to have registered their changes) - m_WaitForChangesTimeout = Time.realtimeSinceStartup + 0.50f; - m_ChangesAppliedToNetworkVariables = true; - } - } + Str = "ManagedNetworkSerializableType", + Ints = new[] { 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000 }, + Embedded = new EmbeddedManagedNetworkSerializableType { Int = 20000 } + }; + + //Set the timeout (i.e. how long we will wait for all NetworkVariables to have registered their changes) + m_WaitForChangesTimeout = Time.realtimeSinceStartup + 0.50f; + m_ChangesAppliedToNetworkVariables = true; + } + else if (!m_FinishedTests) + { + m_FinishedTests = DidAllValuesChange() || (m_WaitForChangesTimeout < Time.realtimeSinceStartup); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs index 44367d5120..0d32710a64 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs @@ -251,9 +251,17 @@ public NetworkVariableTests(Serialization serialization) m_EnsureLengthSafety = serialization == Serialization.EnsureLengthSafety; } + private bool m_CanStart = false; + protected override bool CanStartServerAndClients() { - return false; + return m_CanStart; + } + + protected override void OnInlineSetup() + { + m_CanStart = false; + base.OnInlineSetup(); } protected override void OnOneTimeSetup() @@ -343,21 +351,44 @@ private void InitializeServerAndClients(HostOrServer useHost) [Test] public void AllNetworkVariableTypes([Values] HostOrServer useHost) { + var prefabToSpawn = CreateNetworkObjectPrefab("NetVarTest"); + prefabToSpawn.AddComponent(); + + m_CanStart = true; + StartServerAndClientsWithTimeTravel(); + var authority = GetAuthorityNetworkManager(); + + // Shutdown the other clients + foreach(var networkManager in m_NetworkManagers) + { + if (networkManager == authority) + { + continue; + } + StopOneClientWithTimeTravel(networkManager); + } + // Create, instantiate, and host // This would normally go in Setup, but since every other test but this one // uses NetworkManagerHelper, and it does its own NetworkManager setup / teardown, // for now we put this within this one test until we migrate it to MIH - Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out NetworkManager server, useHost == HostOrServer.Host ? NetworkManagerHelper.NetworkManagerOperatingMode.Host : NetworkManagerHelper.NetworkManagerOperatingMode.Server)); + //Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out NetworkManager server, useHost == HostOrServer.Host ? NetworkManagerHelper.NetworkManagerOperatingMode.Host : NetworkManagerHelper.NetworkManagerOperatingMode.Server)); - Assert.IsTrue(server.IsHost == (useHost == HostOrServer.Host), $"{nameof(useHost)} does not match the server.IsHost value!"); + //Assert.IsTrue(server.IsHost == (useHost == HostOrServer.Host), $"{nameof(useHost)} does not match the server.IsHost value!"); - Guid gameObjectId = NetworkManagerHelper.AddGameNetworkObject("NetworkVariableTestComponent"); + //Guid gameObjectId = NetworkManagerHelper.AddGameNetworkObject("NetworkVariableTestComponent"); - var networkVariableTestComponent = NetworkManagerHelper.AddComponentToObject(gameObjectId); + //var networkVariableTestComponent = NetworkManagerHelper.AddComponentToObject(gameObjectId); - NetworkManagerHelper.SpawnNetworkObject(gameObjectId); + //NetworkManagerHelper.SpawnNetworkObject(gameObjectId); + + var instance = SpawnObject(prefabToSpawn, authority); + var networkVariableTestComponent = instance.GetComponent(); + + Assert.IsTrue(networkVariableTestComponent.IsSpawned, $"Failed to spawn {instance.name}!"); // Start Testing + networkVariableTestComponent.InitializeTest(); networkVariableTestComponent.EnableTesting = true; var success = WaitForConditionOrTimeOutWithTimeTravel(() => true == networkVariableTestComponent.IsTestComplete()); @@ -370,12 +401,15 @@ public void AllNetworkVariableTypes([Values] HostOrServer useHost) networkVariableTestComponent.AssertAllValuesAreCorrect(); // Disable this once we are done. - networkVariableTestComponent.gameObject.SetActive(false); + + + // This would normally go in Teardown, but since every other test but this one // uses NetworkManagerHelper, and it does its own NetworkManager setup / teardown, // for now we put this within this one test until we migrate it to MIH - NetworkManagerHelper.ShutdownNetworkManager(); + StopOneClientWithTimeTravel(authority); + //NetworkManagerHelper.ShutdownNetworkManager(); } [Test] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs index 0c5895b612..09d1e8c7b4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs @@ -35,7 +35,10 @@ public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() var networkObjectToSpawnOnClient = m_ClientSideValidPrefab.GetComponent(); nonAuthority.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, new NetworkPrefabExceptionThrower()); - nonAuthority.PrefabHandler.AddHandler(m_ValidPrefab, new NetworkPrefabInstanceHandler(networkObjectToSpawnOnClient)); + var prefabHandlerObject = new GameObject(); + var prefabHandler = prefabHandlerObject.AddComponent(); + prefabHandler.Initialize(nonAuthority, m_ValidPrefab.GetComponent()); + //nonAuthority.PrefabHandler.AddHandler(m_ValidPrefab, new NetworkPrefabInstanceHandler(networkObjectToSpawnOnClient)); var authority = GetAuthorityNetworkManager(); @@ -60,8 +63,14 @@ public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() // Create a new client and register the same PrefabHandlers on the client var newClient = CreateNewClient(); + var prefabHandlerObject2 = new GameObject(); + var prefabHandler2 = prefabHandlerObject2.AddComponent(); + newClient.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, new NetworkPrefabExceptionThrower()); - newClient.PrefabHandler.AddHandler(m_ValidPrefab, new NetworkPrefabInstanceHandler(networkObjectToSpawnOnClient)); + + var prefabHandlerObject3 = new GameObject(); + var prefabHandler3 = prefabHandlerObject3.AddComponent(); + prefabHandler3.Initialize(nonAuthority, networkObjectToSpawnOnClient); // Expect assertions from the new client LogAssert.Expect(LogType.Exception, "Exception: exception while instantiating"); @@ -92,6 +101,8 @@ public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() Assert.That(networkManager.SpawnManager.SpawnedObjects.ContainsKey(exceptionObject.NetworkObjectId), Is.False, "Non authority should not have spawned exception object!"); } } + + Object.Destroy(prefabHandlerObject); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs index 1e4f907180..c8163987c9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs @@ -1,6 +1,6 @@ using System; +using System.Collections; using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; @@ -15,20 +15,35 @@ namespace Unity.Netcode.RuntimeTests /// Destroying a newly spawned NetworkObject instance works /// Removing a INetworkPrefabInstanceHandler is removed and can be verified (very last check) /// - internal class NetworkPrefabHandlerTests + internal class NetworkPrefabHandlerTests : NetcodeIntegrationTest { - [OneTimeSetUp] - public void OneTimeSetup() + protected override int NumberOfClients => 0; + + protected override void OnOneTimeSetup() { // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); + base.OnOneTimeSetup(); } private const string k_TestPrefabObjectName = "NetworkPrefabTestObject"; - private uint m_ObjectId = 1; + private uint m_ObjectId = 0; + + private bool m_CanStart; + + + + protected override bool CanStartServerAndClients() + { + return m_CanStart; + } + private GameObject MakeValidNetworkPrefab() { - return GenerateAndRegisterPrefab(); + m_ObjectId++; + return CreateNetworkObjectPrefab(k_TestPrefabObjectName + m_ObjectId.ToString()); + + //return GenerateAndRegisterPrefab(); //Guid baseObjectID = NetworkManagerHelper.AddGameNetworkObject(k_TestPrefabObjectName + m_ObjectId.ToString()); //NetworkObject validPrefab = NetworkManagerHelper.InstantiatedNetworkObjects[baseObjectID]; //NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(validPrefab); @@ -52,94 +67,112 @@ private GameObject GenerateAndRegisterPrefab() /// Tests the NetwokConfig NetworkPrefabsList initialization during NetworkManager's Init method to make sure that /// it will still initialize but remove the invalid prefabs /// - [Test] - public void NetworkConfigInvalidNetworkPrefabTest() + [UnityTest] + public IEnumerator NetworkConfigInvalidNetworkPrefabTest() { + var authority = GetAuthorityNetworkManager(); // Add null entry - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(null); + authority.NetworkConfig.Prefabs.Add(null); // Add a NetworkPrefab with no prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab()); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab()); // Add a NetworkPrefab override with an invalid hash - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 0 }); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 0 }); // Add a NetworkPrefab override with a valid hash but an invalid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 654321, OverridingTargetPrefab = null }); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 654321, OverridingTargetPrefab = null }); // Add a NetworkPrefab override with a valid hash to override but an invalid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourceHashToOverride = 654321, OverridingTargetPrefab = null }); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourceHashToOverride = 654321, OverridingTargetPrefab = null }); // Add a NetworkPrefab override with an invalid source prefab to override - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = null }); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = null }); // Create a valid network prefab "asset". var validPrefabAsset = MakeValidNetworkPrefab().GetComponent(); // Add a NetworkPrefab override with a valid source prefab to override but an invalid target prefab. - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = validPrefabAsset.gameObject, OverridingTargetPrefab = null }); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = validPrefabAsset.gameObject, OverridingTargetPrefab = null }); // Now add the valid asset as a network prefab with no override. - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = validPrefabAsset.gameObject }); + //authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = validPrefabAsset.gameObject }); var validPrefabForSourceHash = MakeValidNetworkPrefab().GetComponent(); // This would be the scenario that a hash would be used (typically when scene management is disabled) validPrefabForSourceHash.InScenePlaced = true; - - // Add a NetworkPrefab override with a valid hash and valid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = validPrefabForSourceHash.GlobalObjectIdHash, OverridingTargetPrefab = validPrefabAsset.gameObject }); + var networkPrefab = authority.NetworkConfig.Prefabs.InternalPrefabs[authority.NetworkConfig.Prefabs.InternalPrefabs.Count - 1]; + networkPrefab.SourceHashToOverride = validPrefabForSourceHash.GlobalObjectIdHash; + networkPrefab.OverridingTargetPrefab = validPrefabAsset.gameObject; + networkPrefab.Override = NetworkPrefabOverride.Hash; + authority.NetworkConfig.Prefabs.InternalPrefabs[authority.NetworkConfig.Prefabs.InternalPrefabs.Count - 1] = networkPrefab; + + // Add a NetworkPrefab override with a valid hash and valid target prefab + //authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = validPrefabForSourceHash.GlobalObjectIdHash, OverridingTargetPrefab = validPrefabAsset.gameObject }); + var sourcePrefab = MakeValidNetworkPrefab(); + networkPrefab = authority.NetworkConfig.Prefabs.InternalPrefabs[authority.NetworkConfig.Prefabs.InternalPrefabs.Count - 1]; + var index = authority.NetworkConfig.Prefabs.Prefabs.Count - 1; + var targetPrefab = MakeValidNetworkPrefab(); + networkPrefab.Prefab = sourcePrefab; + networkPrefab.SourcePrefabToOverride = sourcePrefab; + networkPrefab.OverridingTargetPrefab = targetPrefab; + authority.NetworkConfig.Prefabs.InternalPrefabs[index] = networkPrefab; // Add a NetworkPrefab override with a valid prefab and valid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = MakeValidNetworkPrefab(), OverridingTargetPrefab = MakeValidNetworkPrefab() }); + //authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = MakeValidNetworkPrefab(), OverridingTargetPrefab = MakeValidNetworkPrefab() }); - var exceptionOccurred = false; - try - { - NetworkManagerHelper.NetworkManagerObject.StartHost(); - } - catch - { - exceptionOccurred = true; - } + m_CanStart = true; + yield return StartServerAndClients(); - Assert.False(exceptionOccurred); + //var exceptionOccurred = false; + //try + //{ + // Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out var server, NetworkManagerHelper.NetworkManagerOperatingMode.Host, authority.NetworkConfig), "Failed to start host!"); + //} + //catch + //{ + // exceptionOccurred = true; + //} + + //Assert.False(exceptionOccurred); // In the end we should only have 3 valid registered network prefabs - Assert.AreEqual(3, NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Count); + Assert.AreEqual(5, authority.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Count); } private const string k_PrefabObjectName = "NetworkPrefabHandlerTestObject"; - [Test] - public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType) + + [UnityTest] + public IEnumerator NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType) { - var networkConfig = new NetworkConfig() - { - NetworkTopology = topologyType, - }; + var authority = GetAuthorityNetworkManager(); + authority.NetworkConfig.NetworkTopology = topologyType; + var baseObject = MakeValidNetworkPrefab().GetComponent(); - Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _, networkConfig: networkConfig)); - var testPrefabObjectName = k_PrefabObjectName; + m_CanStart = true; + yield return StartServerAndClients(); - Guid baseObjectID = NetworkManagerHelper.AddGameNetworkObject(testPrefabObjectName); - NetworkObject baseObject = NetworkManagerHelper.InstantiatedNetworkObjects[baseObjectID]; + var testPrefabObjectName = k_TestPrefabObjectName; - var networkPrefabHandler = new NetworkPrefabHandler(); - var networkPrefabInstanceHandler = new NetworkPrefabInstanceHandler(baseObject); + var networkPrefabHandler = authority.PrefabHandler; + var prefabHandlerObject = new GameObject(); + var networkPrefabInstanceHandler = prefabHandlerObject.AddComponent(); + networkPrefabInstanceHandler.Initialize(authority, baseObject); var prefabPosition = new Vector3(1.0f, 5.0f, 3.0f); var prefabRotation = new Quaternion(1.0f, 0.5f, 0.4f, 0.1f); //Register via GameObject - var gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.gameObject, networkPrefabInstanceHandler); + var gameObjectRegistered = authority.PrefabHandler.ContainsHandler(baseObject); //Test result of registering via GameObject reference Assert.True(gameObjectRegistered); - var spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + var spawnedObject = authority.PrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); //Test that something was instantiated Assert.NotNull(spawnedObject); @@ -151,11 +184,11 @@ public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType Assert.True(prefabPosition == spawnedObject.transform.position); Assert.True(prefabRotation == spawnedObject.transform.rotation); - networkPrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance - networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler + authority.PrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance + authority.PrefabHandler.RemoveHandler(baseObject); //Remove our handler //Register via NetworkObject - gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject, networkPrefabInstanceHandler); + gameObjectRegistered = authority.PrefabHandler.AddHandler(baseObject, networkPrefabInstanceHandler); //Test result of registering via NetworkObject reference Assert.True(gameObjectRegistered); @@ -164,7 +197,7 @@ public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType prefabPosition = new Vector3(2.0f, 1.0f, 5.0f); prefabRotation = new Quaternion(4.0f, 1.5f, 5.4f, 5.1f); - spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + spawnedObject = authority.PrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); //Test that something was instantiated Assert.NotNull(spawnedObject); @@ -176,11 +209,11 @@ public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType Assert.True(prefabPosition == spawnedObject.transform.position); Assert.True(prefabRotation == spawnedObject.transform.rotation); - networkPrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance - networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler + authority.PrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance + authority.PrefabHandler.RemoveHandler(baseObject); //Remove our handler //Register via GlobalObjectIdHash - gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.GlobalObjectIdHash, networkPrefabInstanceHandler); + gameObjectRegistered = authority.PrefabHandler.AddHandler(baseObject.GlobalObjectIdHash, networkPrefabInstanceHandler); //Test result of registering via GlobalObjectIdHash reference Assert.True(gameObjectRegistered); @@ -189,7 +222,7 @@ public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType prefabPosition = new Vector3(6.0f, 4.0f, 1.0f); prefabRotation = new Quaternion(3f, 2f, 4f, 1f); - spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + spawnedObject = authority.PrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); //Test that something was instantiated Assert.NotNull(spawnedObject); @@ -201,59 +234,69 @@ public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType Assert.True(prefabPosition == spawnedObject.transform.position); Assert.True(prefabRotation == spawnedObject.transform.rotation); - networkPrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance - networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler + authority.PrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance + authority.PrefabHandler.RemoveHandler(baseObject); //Remove our handler // Register a handler that throws an exception var networkPrefabExceptionThrower = new NetworkPrefabExceptionThrower(); - gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject, networkPrefabExceptionThrower); + gameObjectRegistered = authority.PrefabHandler.AddHandler(baseObject, networkPrefabExceptionThrower); //Test result of registering exception handler Assert.True(gameObjectRegistered); LogAssert.Expect(LogType.Exception, "Exception: exception while instantiating"); - spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + spawnedObject = authority.PrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); // No object should have been spawned, but test should have continued Assert.Null(spawnedObject); - networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler + authority.PrefabHandler.RemoveHandler(baseObject); //Remove our handler Assert.False(networkPrefabInstanceHandler.StillHasInstances()); - } - [SetUp] - public void Setup() - { - //Create, instantiate, and host - NetworkManagerHelper.StartNetworkManager(out _, NetworkManagerHelper.NetworkManagerOperatingMode.None); + UnityEngine.Object.Destroy(prefabHandlerObject); } - [TearDown] - public void TearDown() + //[SetUp] + //public void Setup() + //{ + // //Create, instantiate, and host + // NetworkManagerHelper.StartNetworkManager(out _, NetworkManagerHelper.NetworkManagerOperatingMode.None); + //} + + protected override IEnumerator OnTearDown() { - //Stop, shutdown, and destroy - NetworkManagerHelper.ShutdownNetworkManager(); - var networkObjects = FindObjects.ByType(); - var networkObjectsList = networkObjects.Where(c => c.name.Contains(k_PrefabObjectName)); - foreach (var networkObject in networkObjectsList) - { - UnityEngine.Object.DestroyImmediate(networkObject); - } + m_CanStart = false; + return base.OnTearDown(); } + + //[TearDown] + //public void TearDown() + //{ + // //Stop, shutdown, and destroy + // NetworkManagerHelper.ShutdownNetworkManager(); + // var networkObjects = FindObjects.ByType(); + // var networkObjectsList = networkObjects.Where(c => c.name.Contains(k_PrefabObjectName)); + // foreach (var networkObject in networkObjectsList) + // { + // UnityEngine.Object.DestroyImmediate(networkObject); + // } + //} } /// /// The Prefab instance handler to use for this test /// - internal class NetworkPrefabInstanceHandler : INetworkPrefabInstanceHandler + internal class NetworkPrefabInstanceHandler : MonoBehaviour, INetworkPrefabInstanceHandler { private NetworkObject m_NetworkObject; private List m_Instances; + private NetworkManager m_NetworkManager; + public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) { - var networkObjectInstance = UnityEngine.Object.Instantiate(m_NetworkObject.gameObject).GetComponent(); + var networkObjectInstance = Instantiate(m_NetworkObject.gameObject).GetComponent(); networkObjectInstance.transform.SetPositionAndRotation(position, rotation); m_Instances.Add(networkObjectInstance); return networkObjectInstance; @@ -261,10 +304,13 @@ public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaterni public void Destroy(NetworkObject networkObject) { - var instancesContainsNetworkObject = m_Instances.Contains(networkObject); - Assert.True(instancesContainsNetworkObject); - m_Instances.Remove(networkObject); - UnityEngine.Object.Destroy(networkObject.gameObject); + if (m_Instances == null || m_Instances.Count > 0) + { + var instancesContainsNetworkObject = m_Instances.Contains(networkObject); + Assert.True(instancesContainsNetworkObject); + m_Instances.Remove(networkObject); + Destroy(networkObject.gameObject); + } } public bool StillHasInstances() @@ -272,17 +318,29 @@ public bool StillHasInstances() return (m_Instances.Count > 0); } - public NetworkPrefabInstanceHandler(NetworkObject networkObject) + private void OnDestroy() + { + if (m_NetworkManager != null) + { + m_NetworkManager.PrefabHandler.RemoveHandler(m_NetworkObject); + } + m_Instances.Clear(); + m_Instances = null; + } + + public void Initialize(NetworkManager networkManager, NetworkObject networkObject) { + m_NetworkManager = networkManager; m_NetworkObject = networkObject; m_Instances = new List(); + networkManager.PrefabHandler.AddHandler(networkObject, this); } } /// /// Causes an exception during client connection /// - internal class NetworkPrefabExceptionThrower : INetworkPrefabInstanceHandler + internal class NetworkPrefabExceptionThrower : MonoBehaviour, INetworkPrefabInstanceHandler { public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) { @@ -291,7 +349,7 @@ public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaterni public void Destroy(NetworkObject networkObject) { - UnityEngine.Object.Destroy(networkObject.gameObject); + Destroy(networkObject.gameObject); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs index 7b06e49576..76d6467a04 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs @@ -24,7 +24,7 @@ public NetworkPrefabHandlerWithDataTests(NetworkTopologyTypes topology) : base(t protected override void OnServerAndClientsCreated() { // Creates a network object prefab and registers it to all clients. - m_Prefab = CreateNetworkObjectPrefab(k_TestPrefabObjectName).gameObject; + m_Prefab = CreateNetworkObjectPrefab(k_TestPrefabObjectName); var authority = GetAuthorityNetworkManager(); m_ClientHandlers = new PrefabInstanceHandlerWithData[NumberOfClients]; @@ -67,8 +67,13 @@ public IEnumerator InstantiationPayload_LateJoinersReceiveData() var data = new NetworkSerializableTest { Value = 42, Value2 = 2.71f }; var spawned = SpawnPrefabWithData(data); + Debug.Log("Spawn"); yield return WaitForConditionOrTimeOut(() => AllHandlersSynchronized(data)); AssertOnTimeout("Not all handlers synchronized"); + yield return WaitForSpawnedOnAllOrTimeOut(spawned); + AssertOnTimeout($"Not all clients spawned {spawned.name}!"); + + // When running with Distributed Authority, test a late-joiner after an ownership change // The object owner will synchronize the late joining client, showing that the instantiationData will survive host migration. @@ -88,7 +93,7 @@ public IEnumerator InstantiationPayload_LateJoinersReceiveData() }); AssertOnTimeout($"Timed out while waiting for Client-{newOwner.LocalClientId} to own object"); } - + Debug.Log("Late client..."); // Late join a client yield return CreateAndStartNewClient(); @@ -105,9 +110,12 @@ private void RegisterPrefabHandler(NetworkManager manager, out PrefabInstanceHan private NetworkObject SpawnPrefabWithData(NetworkSerializableTest data) { + var authority = GetAuthorityNetworkManager(); var instance = UnityEngine.Object.Instantiate(m_Prefab).GetComponent(); + GetAuthorityNetworkManager().PrefabHandler.SetInstantiationData(instance, data); - instance.Spawn(); + + SpawnObjectInstance(instance, authority); return instance; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs index c0b870193a..56de84deb9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs @@ -1,41 +1,41 @@ -using System; +using System.Collections; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests { - internal sealed class NetworkVariableNameTests + internal class NetworkVariableNameTests : NetcodeIntegrationTest { + protected override int NumberOfClients => 1; private NetworkVariableNameComponent m_NetworkVariableNameComponent; - [OneTimeSetUp] - public void OneTimeSetup() - { - // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. - NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); - } + private GameObject m_PrefabToTest; - [SetUp] - public void SetUp() + protected override void OnServerAndClientsCreated() { - NetworkManagerHelper.StartNetworkManager(out _); - - var gameObjectId = NetworkManagerHelper.AddGameNetworkObject(Guid.NewGuid().ToString()); - m_NetworkVariableNameComponent = NetworkManagerHelper.AddComponentToObject(gameObjectId); - NetworkManagerHelper.SpawnNetworkObject(gameObjectId); + m_PrefabToTest = CreateNetworkObjectPrefab("NetVarNameTest"); + m_PrefabToTest.AddComponent(); + base.OnServerAndClientsCreated(); } - [TearDown] - public void TearDown() + [UnityTest] + public IEnumerator VerifyNetworkVariableNameInitialization() { - NetworkManagerHelper.ShutdownNetworkManager(); - } + var authority = GetAuthorityNetworkManager(); + var authorityInstance = SpawnObject(m_PrefabToTest, authority); + var authorityNetworkObject = authorityInstance.GetComponent(); - [Test] - public void VerifyNetworkVariableNameInitialization() - { - // Fields have regular naming - Assert.AreEqual(nameof(NetworkVariableNameComponent.NetworkVarList), m_NetworkVariableNameComponent.NetworkVarList.Name); + yield return WaitForSpawnedOnAllOrTimeOut(authorityInstance); + AssertOnTimeout($"Not all clients spawned {authorityInstance.name}!"); + + foreach (var networkManager in m_NetworkManagers) + { + var componentInstance = networkManager.SpawnManager.SpawnedObjects[authorityNetworkObject.NetworkObjectId].GetComponent(); + // Verify fields have regular naming + Assert.AreEqual(nameof(NetworkVariableNameComponent.NetworkVarList), componentInstance.NetworkVarList.Name); + } } private class NetworkVariableNameComponent : NetworkBehaviour diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs index 1489fb61b9..94741ad3e5 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs @@ -1,6 +1,4 @@ -using System; using System.Collections; -using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; @@ -13,62 +11,47 @@ namespace Unity.Netcode.RuntimeTests /// - That all RPCs invoke at the appropriate `NetworkUpdateStage` (Client and Server) /// - A lower level `MessageQueueContainer` test that validates `MessageQueueFrameItems` after they have been put into the queue /// - internal class RpcQueueTests + internal class RpcQueueTests : NetcodeIntegrationTest { - [OneTimeSetUp] - public void OneTimeSetup() + protected override int NumberOfClients => 0; + + private GameObject m_TestPrefab; + + protected override void OnOneTimeSetup() { // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); + base.OnOneTimeSetup(); } - [SetUp] - public void Setup() + protected override void OnServerAndClientsCreated() { - // Create, instantiate, and host - Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _)); + m_TestPrefab = CreateNetworkObjectPrefab("RpcQueueTest"); + m_TestPrefab.AddComponent(); + base.OnServerAndClientsCreated(); } /// /// This tests the RPC Queue outbound and inbound buffer capabilities. /// /// IEnumerator - [UnityTest, Order(2)] + [UnityTest] public IEnumerator BufferDataValidation() { - Guid gameObjectId = NetworkManagerHelper.AddGameNetworkObject("GrowingBufferObject"); + var authority = GetAuthorityNetworkManager(); + var instance = SpawnObject(m_TestPrefab, authority); - var growingRpcBufferSizeComponent = NetworkManagerHelper.AddComponentToObject(gameObjectId); + yield return WaitForSpawnedOnAllOrTimeOut(instance); + AssertOnTimeout($"Not all clients spawned {instance.name}!"); - NetworkManagerHelper.SpawnNetworkObject(gameObjectId); + var bufferDataValidationComponent = instance.GetComponent(); // Start Testing - growingRpcBufferSizeComponent.EnableTesting = true; - - var testsAreComplete = growingRpcBufferSizeComponent.IsTestComplete(); - - // Wait for the RPC pipeline test to complete or if we exceeded the maximum iterations bail - while (!testsAreComplete) - { - yield return new WaitForSeconds(0.003f); - - testsAreComplete = growingRpcBufferSizeComponent.IsTestComplete(); - } + bufferDataValidationComponent.EnableTesting = true; - // Stop Testing - growingRpcBufferSizeComponent.EnableTesting = false; + yield return WaitForConditionOrTimeOut(() => bufferDataValidationComponent.IsTestComplete()); + AssertOnTimeout($"Timed out waiting for the {nameof(BufferDataValidationComponent)} tests to complete!"); - // Just disable this once we are done. - growingRpcBufferSizeComponent.gameObject.SetActive(false); - - Assert.IsTrue(testsAreComplete); - } - - [TearDown] - public void TearDown() - { - // Stop, shutdown, and destroy - NetworkManagerHelper.ShutdownNetworkManager(); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs new file mode 100644 index 0000000000..dabc67996b --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs @@ -0,0 +1,373 @@ +using System.Collections; +using System.Runtime.CompilerServices; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; + + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(HostOrServer.DAHost)] + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class BaseReferenceTests : NetcodeIntegrationTest + { + protected struct GroupedComponents + { + public GameObject GameObject; + public NetworkObject NetworkObject; + public TestNetworkBehaviour TestNetworkBehaviour; + } + + protected override int NumberOfClients => 1; + + protected GameObject m_TestPrefab; + + protected GroupedComponents m_ValidatingInstance + { + get; + private set; + } + protected GroupedComponents m_ReferenceToUse + { + get; + private set; + } + + public BaseReferenceTests(HostOrServer hostOrServer) : base(hostOrServer) + { + } + + protected override void OnServerAndClientsCreated() + { + m_TestPrefab = CreateNetworkObjectPrefab("ReferenceTest"); + m_TestPrefab.AddComponent(); + base.OnServerAndClientsCreated(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected GroupedComponents GetGroup(GameObject gameObject) + { + return new GroupedComponents() + { + GameObject = gameObject, + NetworkObject = gameObject.GetComponent(), + TestNetworkBehaviour = gameObject.GetComponent() + }; + } + + protected IEnumerator SpawnTestPrefabInstance(bool spawnSingle = false) + { + var authority = GetAuthorityNetworkManager(); + m_ValidatingInstance = GetGroup(SpawnObject(m_TestPrefab, authority)); + + yield return WaitForSpawnedOnAllOrTimeOut(m_ValidatingInstance.GameObject); + AssertOnTimeout($"[{GetType().Name}][Validating Instance] Faild to spawn {m_ValidatingInstance.GameObject.name} on all clients!"); + + if (!spawnSingle) + { + m_ReferenceToUse = GetGroup(SpawnObject(m_TestPrefab, authority)); + + yield return WaitForSpawnedOnAllOrTimeOut(m_ReferenceToUse.GameObject); + AssertOnTimeout($"[{GetType().Name}][Reference to use] Faild to spawn {m_ReferenceToUse.GameObject.name} on all clients!"); + } + } + + #region NetworkBehaviour specific conditional methods + protected bool RpcWasReceivedAndBehaviourValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var authorityReferenceId = m_ReferenceToUse.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + var referenceNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject) + && networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityReferenceId, out referenceNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + var referenceBehaviour = referenceNetworkObject.GetComponent(); + + if (!validatingBehaviour.ReceivedRPC) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the RPC!"); + continue; + } + if (!validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} failed to acquire the reference!"); + continue; + } + if (referenceBehaviour != validatingBehaviour.RpcReceivedBehaviour) + { + var currentReferenceBehaviour = validatingBehaviour.RpcReceivedBehaviour != null ? validatingBehaviour.RpcReceivedBehaviour.name : "null"; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference {referenceBehaviour.name} but was {currentReferenceBehaviour}!"); + } + } + else + { + var wasNotSpawned = validatingNetworkObject == null ? m_ValidatingInstance.GameObject.name : m_ReferenceToUse.GameObject.name; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {wasNotSpawned}!"); + } + validatingNetworkObject = null; + referenceNetworkObject = null; + } + + return stringBuilder.Length == 0; + } + + protected bool NetworkVariableChangedAndBehaviourValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var authorityReferenceId = m_ReferenceToUse.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + var referenceNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject) + && networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityReferenceId, out referenceNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + var referenceBehaviour = referenceNetworkObject.GetComponent(); + + if (!validatingBehaviour.TestVariableChanged) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the NetworkVariable update!"); + continue; + } + if (!validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} failed to acquire a reference!"); + continue; + } + if (referenceBehaviour != validatingBehaviour.TestVariableBehaviour) + { + var currentReferenceBehaviour = validatingBehaviour.TestVariableBehaviour != null ? validatingBehaviour.RpcReceivedBehaviour.name : "null"; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference {referenceBehaviour.name} but was {currentReferenceBehaviour}!"); + } + } + else + { + var wasNotSpawned = validatingNetworkObject == null ? m_ValidatingInstance.GameObject.name : m_ReferenceToUse.GameObject.name; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {wasNotSpawned}!"); + } + validatingNetworkObject = null; + referenceNetworkObject = null; + } + return stringBuilder.Length == 0; + } + + protected bool RpcSerializingNullValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + + if (!validatingBehaviour.ReceivedRPC) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the RPC!"); + continue; + } + if (validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} Acquired a reference when it should not have!"); + continue; + } + if (validatingBehaviour.RpcReceivedBehaviour != null) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference NULL but was {validatingBehaviour.RpcReceivedBehaviour.name}!"); + } + } + else + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {m_ValidatingInstance.GameObject.name}!"); + } + validatingNetworkObject = null; + } + return stringBuilder.Length == 0; + } + + protected bool NetworkVariableSerializingNullValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + + if (!validatingBehaviour.TestVariableChanged) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not detected a change in the NetworkVariable!"); + continue; + } + if (validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} Acquired a reference when it should not have!"); + continue; + } + if (validatingBehaviour.TestVariableBehaviour != null) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference NULL but was {validatingBehaviour.TestVariableBehaviour.name}!"); + } + } + else + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {m_ValidatingInstance.GameObject.name}!"); + } + validatingNetworkObject = null; + } + return stringBuilder.Length == 0; + } + #endregion + + protected bool NetworkObjectSerializedValidation(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var authorityReferenceId = m_ReferenceToUse.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + var referenceNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityReferenceId, out referenceNetworkObject) && + networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + var referenceBehaviour = referenceNetworkObject.GetComponent(); + + if (!validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingNetworkObject.name} failed to acquire the reference!"); + continue; + } + if (referenceNetworkObject != validatingBehaviour.RpcReceivedNetworkObject) + { + var currentReferencedObject = validatingBehaviour.RpcReceivedNetworkObject != null ? validatingBehaviour.RpcReceivedNetworkObject.name : "null"; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected {nameof(NetworkObject)} reference " + + $"{referenceNetworkObject.name} but was {currentReferencedObject}!"); + } + } + else + { + var wasNotSpawned = validatingNetworkObject == null ? m_ValidatingInstance.GameObject.name : m_ReferenceToUse.GameObject.name; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {wasNotSpawned}!"); + } + validatingNetworkObject = null; + referenceNetworkObject = null; + } + + return stringBuilder.Length == 0; + } + + protected bool SerializingNullNetworkObjectValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + + if (!validatingBehaviour.ReceivedRPC) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the RPC!"); + continue; + } + if (validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} Acquired a reference when it should not have!"); + continue; + } + if (validatingBehaviour.RpcReceivedNetworkObject != null) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected {nameof(NetworkObjectReference)} to be NULL but was {validatingBehaviour.RpcReceivedNetworkObject.name}!"); + } + } + else + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {m_ValidatingInstance.GameObject.name}!"); + } + validatingNetworkObject = null; + } + return stringBuilder.Length == 0; + } + + + protected class TestNetworkBehaviour : NetworkBehaviour + { + public bool ReceivedRPC; + public bool TestVariableChanged; + public bool AcquiredReference; + + public NetworkVariable NetworkBehaviourVariable = new NetworkVariable(); + + public TestNetworkBehaviour TestVariableBehaviour; + + public TestNetworkBehaviour RpcReceivedBehaviour; + public TestNetworkBehaviour RpcReceived; + + public NetworkVariable NetworkObjectVariable = new NetworkVariable(); + + public NetworkObject TestVariableNetworkObject; + public GameObject TestVariableGameObject; + + public NetworkObject RpcReceivedNetworkObject; + public GameObject RpcReceivedGameObject; + + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + // Set it to ourself so we can validate serializing null too. + RpcReceivedBehaviour = this; + TestVariableBehaviour = null; + base.OnNetworkPreSpawn(ref networkManager); + } + + public override void OnNetworkSpawn() + { + NetworkBehaviourVariable.OnValueChanged += OnTestVariableChanged; + NetworkObjectVariable.OnValueChanged += OnNetworkObjectVariableChanged; + base.OnNetworkSpawn(); + } + + public override void OnNetworkPreDespawn() + { + NetworkBehaviourVariable.OnValueChanged -= OnTestVariableChanged; + NetworkObjectVariable.OnValueChanged -= OnNetworkObjectVariableChanged; + base.OnNetworkPreDespawn(); + } + private void OnTestVariableChanged(NetworkBehaviourReference previous, NetworkBehaviourReference current) + { + TestVariableChanged = true; + AcquiredReference = current.TryGet(out TestVariableBehaviour, NetworkManager); + } + + private void OnNetworkObjectVariableChanged(NetworkObjectReference previous, NetworkObjectReference current) + { + TestVariableChanged = true; + AcquiredReference = current.TryGet(out TestVariableNetworkObject, NetworkManager); + } + + [Rpc(SendTo.Everyone)] + public void SendNetworkBehaviourReferenceRpc(NetworkBehaviourReference value) + { + AcquiredReference = value.TryGet(out RpcReceivedBehaviour, NetworkManager); + ReceivedRPC = true; + } + + [Rpc(SendTo.Everyone)] + public void SendNetworkObjectReferenceRpc(NetworkObjectReference value) + { + ReceivedRPC = true; + AcquiredReference = value.TryGet(out RpcReceivedNetworkObject, NetworkManager); + RpcReceivedGameObject = value; + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs.meta new file mode 100644 index 0000000000..5d79890713 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 87bdaa820aaed704f8309de710e9153f \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs index fc27d77ff2..20e27d6492 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs @@ -1,10 +1,7 @@ using System; using System.Collections; -using System.Collections.Generic; -using System.Text; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; -using UnityEngine; using UnityEngine.TestTools; using Object = UnityEngine.Object; @@ -12,95 +9,25 @@ namespace Unity.Netcode.RuntimeTests { /// /// Unit tests to test: - /// - Serializing NetworkObject to NetworkObjectReference - /// - Deserializing NetworkObjectReference to NetworkObject - /// - Implicit operators of NetworkObjectReference + /// - Serializing NetworkBehaviour to NetworkBehaviourReference + /// - Deserializing NetworkBehaviourReference to NetworkBehaviour + /// - Implicit operators of NetworkBehaviourReference /// - [TestFixture(HostOrServer.DAHost)] - [TestFixture(HostOrServer.Host)] - [TestFixture(HostOrServer.Server)] - internal class NetworkBehaviourReferenceTests : NetcodeIntegrationTest + internal class NetworkBehaviourReferenceTests : BaseReferenceTests { - protected override int NumberOfClients => 1; - - private GameObject m_TestPrefab; - - private TestNetworkBehaviour m_AuthorityValidatingInstance; - private TestNetworkBehaviour m_BehaviourToUseAsReference; - public NetworkBehaviourReferenceTests(HostOrServer hostOrServer) : base(hostOrServer) { } - protected override void OnServerAndClientsCreated() - { - m_TestPrefab = CreateNetworkObjectPrefab("TestBehaviour"); - m_TestPrefab.AddComponent(); - base.OnServerAndClientsCreated(); - } - #region Tests using non-null NetworkBehaviours and RPCs - private bool RpcWasReceivedAndBehaviourValidated(StringBuilder stringBuilder) - { - var authorityValidatingId = m_AuthorityValidatingInstance.NetworkObjectId; - var authorityReferenceId = m_BehaviourToUseAsReference.NetworkObjectId; - var validatingNetworkObject = (NetworkObject)null; - var referenceNetworkObject = (NetworkObject)null; - foreach (var networkManager in m_NetworkManagers) - { - if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject) - && networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityReferenceId, out referenceNetworkObject)) - { - var validatingBehaviour = validatingNetworkObject.GetComponent(); - var referenceBehaviour = referenceNetworkObject.GetComponent(); - - if (!validatingBehaviour.ReceivedRPC) - { - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the RPC!"); - continue; - } - if (!validatingBehaviour.AcquiredReference) - { - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} failed to acquire the reference!"); - continue; - } - if (referenceBehaviour != validatingBehaviour.RpcReceivedBehaviour) - { - var currentReferenceBehaviour = validatingBehaviour.RpcReceivedBehaviour != null ? validatingBehaviour.RpcReceivedBehaviour.name : "null"; - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference {referenceBehaviour.name} but was {currentReferenceBehaviour}!"); - } - } - else - { - var wasNotSpawned = validatingNetworkObject == null ? m_AuthorityValidatingInstance.name : m_BehaviourToUseAsReference.name; - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {wasNotSpawned}!"); - } - validatingNetworkObject = null; - referenceNetworkObject = null; - } - - return stringBuilder.Length == 0; - } - [UnityTest] public IEnumerator TestRpc() { - var authority = GetAuthorityNetworkManager(); - m_AuthorityValidatingInstance = SpawnObject(m_TestPrefab, authority).GetComponent(); - m_BehaviourToUseAsReference = SpawnObject(m_TestPrefab, authority).GetComponent(); - var objectsToSpawn = new List() - { - m_AuthorityValidatingInstance.NetworkObject, - m_BehaviourToUseAsReference.NetworkObject - }; - - // Spawn the instances - yield return WaitForSpawnedOnAllOrTimeOut(objectsToSpawn); - AssertOnTimeout($"[{nameof(TestSerializeNull)}] Not all clients spawned all instances of {m_AuthorityValidatingInstance.name} or {m_BehaviourToUseAsReference.name}"); + yield return SpawnTestPrefabInstance(); // Explicitly send the NetworkBehaviour as a reference - m_AuthorityValidatingInstance.SendReferenceRpc(new NetworkBehaviourReference(m_BehaviourToUseAsReference)); + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkBehaviourReferenceRpc(new NetworkBehaviourReference(m_ReferenceToUse.TestNetworkBehaviour)); // Validated the reference yield return WaitForConditionOrTimeOut(RpcWasReceivedAndBehaviourValidated); @@ -111,21 +38,10 @@ public IEnumerator TestRpc() [UnityTest] public IEnumerator TestRpcImplicitNetworkBehaviour() { - var authority = GetAuthorityNetworkManager(); - m_AuthorityValidatingInstance = SpawnObject(m_TestPrefab, authority).GetComponent(); - m_BehaviourToUseAsReference = SpawnObject(m_TestPrefab, authority).GetComponent(); - var objectsToSpawn = new List() - { - m_AuthorityValidatingInstance.NetworkObject, - m_BehaviourToUseAsReference.NetworkObject - }; - - // Spawn the instances - yield return WaitForSpawnedOnAllOrTimeOut(objectsToSpawn); - AssertOnTimeout($"[{nameof(TestSerializeNull)}] Not all clients spawned all instances of {m_AuthorityValidatingInstance.name} or {m_BehaviourToUseAsReference.name}"); + yield return SpawnTestPrefabInstance(); // Implicitly send the NetworkBehaviour as a reference - m_AuthorityValidatingInstance.SendReferenceRpc(m_BehaviourToUseAsReference); + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkBehaviourReferenceRpc(m_ReferenceToUse.TestNetworkBehaviour); // Validated the reference yield return WaitForConditionOrTimeOut(RpcWasReceivedAndBehaviourValidated); @@ -134,187 +50,73 @@ public IEnumerator TestRpcImplicitNetworkBehaviour() #endregion #region Tests using non-null NetworkBehaviours and NetworkVariable - private bool NetworkVariableChangedAndBehaviourValidated(StringBuilder stringBuilder) - { - var authorityValidatingId = m_AuthorityValidatingInstance.NetworkObjectId; - var authorityReferenceId = m_BehaviourToUseAsReference.NetworkObjectId; - var validatingNetworkObject = (NetworkObject)null; - var referenceNetworkObject = (NetworkObject)null; - foreach (var networkManager in m_NetworkManagers) - { - if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject) - && networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityReferenceId, out referenceNetworkObject)) - { - var validatingBehaviour = validatingNetworkObject.GetComponent(); - var referenceBehaviour = referenceNetworkObject.GetComponent(); - - if (!validatingBehaviour.TestVariableChanged) - { - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the NetworkVariable update!"); - continue; - } - if (!validatingBehaviour.AcquiredReference) - { - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} failed to acquire a reference!"); - continue; - } - if (referenceBehaviour != validatingBehaviour.TestVariableBehaviour) - { - var currentReferenceBehaviour = validatingBehaviour.TestVariableBehaviour != null ? validatingBehaviour.RpcReceivedBehaviour.name : "null"; - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference {referenceBehaviour.name} but was {currentReferenceBehaviour}!"); - } - } - else - { - var wasNotSpawned = validatingNetworkObject == null ? m_AuthorityValidatingInstance.name : m_BehaviourToUseAsReference.name; - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {wasNotSpawned}!"); - } - validatingNetworkObject = null; - referenceNetworkObject = null; - } - return stringBuilder.Length == 0; - } - - [UnityTest] public IEnumerator TestNetworkVariable() { - var authority = GetAuthorityNetworkManager(); - m_AuthorityValidatingInstance = SpawnObject(m_TestPrefab, authority).GetComponent(); - m_BehaviourToUseAsReference = SpawnObject(m_TestPrefab, authority).GetComponent(); - var objectsToSpawn = new List() - { - m_AuthorityValidatingInstance.NetworkObject, - m_BehaviourToUseAsReference.NetworkObject - }; - - // Spawn the instances - yield return WaitForSpawnedOnAllOrTimeOut(objectsToSpawn); - AssertOnTimeout($"[{nameof(TestSerializeNull)}] Not all clients spawned all instances of {m_AuthorityValidatingInstance.name} or {m_BehaviourToUseAsReference.name}"); + yield return SpawnTestPrefabInstance(); // Assure the authority instance's value is the default (null) value - Assert.IsNull((NetworkBehaviour)m_AuthorityValidatingInstance.TestVariable.Value); + Assert.IsNull((NetworkBehaviour)m_ValidatingInstance.TestNetworkBehaviour.NetworkBehaviourVariable.Value); // Implicitly assign the NetworkBehaviourReference by assigning the NetworkBehaviour to the NetworkVariable. - m_AuthorityValidatingInstance.TestVariable.Value = m_BehaviourToUseAsReference; + m_ValidatingInstance.TestNetworkBehaviour.NetworkBehaviourVariable.Value = m_ReferenceToUse.TestNetworkBehaviour; // Validated the NetworkVariable reference propogates to clients yield return WaitForConditionOrTimeOut(NetworkVariableChangedAndBehaviourValidated); - AssertOnTimeout($"[{nameof(TestRpc)}] Failed to validate reference!"); + AssertOnTimeout($"[{nameof(TestNetworkVariable)}] Failed to validate reference!"); } #endregion #region Validating using NULL as a NetworkBehaviourReference - private bool RpcSerializingNullValidated(StringBuilder stringBuilder) - { - var authorityValidatingId = m_AuthorityValidatingInstance.NetworkObjectId; - var validatingNetworkObject = (NetworkObject)null; - foreach (var networkManager in m_NetworkManagers) - { - if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) - { - var validatingBehaviour = validatingNetworkObject.GetComponent(); - - if (!validatingBehaviour.ReceivedRPC) - { - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the RPC!"); - continue; - } - if (validatingBehaviour.AcquiredReference) - { - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} Acquired a reference when it should not have!"); - continue; - } - if (validatingBehaviour.RpcReceivedBehaviour != null) - { - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference NULL but was {validatingBehaviour.RpcReceivedBehaviour.name}!"); - } - } - else - { - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {m_AuthorityValidatingInstance.name}!"); - } - validatingNetworkObject = null; - } - return stringBuilder.Length == 0; - } - - private bool NetworkVariableSerializingNullValidated(StringBuilder stringBuilder) - { - var authorityValidatingId = m_AuthorityValidatingInstance.NetworkObjectId; - var validatingNetworkObject = (NetworkObject)null; - foreach (var networkManager in m_NetworkManagers) - { - if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) - { - var validatingBehaviour = validatingNetworkObject.GetComponent(); - - if (!validatingBehaviour.TestVariableChanged) - { - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not detected a change in the NetworkVariable!"); - continue; - } - if (validatingBehaviour.AcquiredReference) - { - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} Acquired a reference when it should not have!"); - continue; - } - if (validatingBehaviour.TestVariableBehaviour != null) - { - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference NULL but was {validatingBehaviour.TestVariableBehaviour.name}!"); - } - } - else - { - stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {m_AuthorityValidatingInstance.name}!"); - } - validatingNetworkObject = null; - } - return stringBuilder.Length == 0; - } - - [UnityTest] public IEnumerator TestSerializeNull() { - var authority = GetAuthorityNetworkManager(); - m_AuthorityValidatingInstance = SpawnObject(m_TestPrefab, authority).GetComponent(); - yield return WaitForSpawnedOnAllOrTimeOut(m_AuthorityValidatingInstance.gameObject); - AssertOnTimeout($"[{nameof(TestSerializeNull)}] Not all clients spawned {m_AuthorityValidatingInstance.name}"); + yield return SpawnTestPrefabInstance(true); + // Initialize with NULL parameter var initializeWithNull = new NetworkBehaviourReference(null); + // Initialize with no parameter var initializeWithNothing = new NetworkBehaviourReference(); - m_AuthorityValidatingInstance.SendReferenceRpc(initializeWithNull); + // Initialized with NULL parameter + // Explicitly send the NetworkBehaviour as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkBehaviourReferenceRpc(initializeWithNull); + + // Validated the reference yield return WaitForConditionOrTimeOut(RpcSerializingNullValidated); - AssertOnTimeout($"[{nameof(TestSerializeNull)}][Initialize with null parameter] Failed to validate null {nameof(NetworkBehaviour)} reference!"); + AssertOnTimeout($"[{nameof(TestRpc)}] Failed to validate reference!"); // Reset the RPC NetworkBehaviourReference to the local instance for all spawned instances. foreach (var networkManager in m_NetworkManagers) { - var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_AuthorityValidatingInstance.NetworkObjectId].GetComponent(); + var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_ValidatingInstance.NetworkObject.NetworkObjectId].GetComponent(); testBehaviour.RpcReceivedBehaviour = testBehaviour; } - // Initialize with no parameter - m_AuthorityValidatingInstance.SendReferenceRpc(initializeWithNothing); + // Initialized with no parameter + // Explicitly send the NetworkBehaviour as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkBehaviourReferenceRpc(initializeWithNothing); + + // Validated the reference yield return WaitForConditionOrTimeOut(RpcSerializingNullValidated); - AssertOnTimeout($"[{nameof(TestSerializeNull)}][Initialize with no parameter] Failed to validate null {nameof(NetworkBehaviour)} reference!"); + AssertOnTimeout($"[{nameof(TestRpc)}] Failed to validate reference!"); + - m_AuthorityValidatingInstance.TestVariable.Value = initializeWithNull; + // Initialize NetworkBehaviourVariable with NULL parameter + m_ValidatingInstance.TestNetworkBehaviour.NetworkBehaviourVariable.Value = initializeWithNull; yield return WaitForConditionOrTimeOut(NetworkVariableSerializingNullValidated); AssertOnTimeout($"[{nameof(TestSerializeNull)}][Initialize with null parameter] Failed to validate null {nameof(NetworkBehaviour)} reference!"); // Reset the NetworkVaraible NetworkBehaviourReference to the local instance for all spawned instances. foreach (var networkManager in m_NetworkManagers) { - var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_AuthorityValidatingInstance.NetworkObjectId].GetComponent(); + var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_ValidatingInstance.NetworkObject.NetworkObjectId].GetComponent(); testBehaviour.TestVariableBehaviour = testBehaviour; testBehaviour.TestVariableChanged = false; } - // Initialize with no parameter - m_AuthorityValidatingInstance.TestVariable.Value = initializeWithNothing; + // Initialize NetworkBehaviourVariable with no parameter + m_ValidatingInstance.TestNetworkBehaviour.NetworkBehaviourVariable.Value = initializeWithNothing; yield return WaitForConditionOrTimeOut(NetworkVariableSerializingNullValidated); AssertOnTimeout($"[{nameof(TestSerializeNull)}][Initialize with no parameter] Failed to validate null {nameof(NetworkBehaviour)} reference!"); } @@ -353,56 +155,6 @@ public IEnumerator FailSerializeGameObjectWithoutNetworkObject() Object.Destroy(instance); } - - #endregion - - #region TestNetworkBehaviour component script - - private class TestNetworkBehaviour : NetworkBehaviour - { - public bool ReceivedRPC; - public bool TestVariableChanged; - public bool AcquiredReference; - - public NetworkVariable TestVariable = new NetworkVariable(); - - public TestNetworkBehaviour TestVariableBehaviour; - - public TestNetworkBehaviour RpcReceivedBehaviour; - - protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) - { - // Set it to ourself so we can validate serializing null too. - RpcReceivedBehaviour = this; - TestVariableBehaviour = null; - base.OnNetworkPreSpawn(ref networkManager); - } - - public override void OnNetworkSpawn() - { - TestVariable.OnValueChanged += OnTestVariableChanged; - base.OnNetworkSpawn(); - } - - public override void OnNetworkPreDespawn() - { - TestVariable.OnValueChanged -= OnTestVariableChanged; - base.OnNetworkPreDespawn(); - } - - private void OnTestVariableChanged(NetworkBehaviourReference previous, NetworkBehaviourReference current) - { - TestVariableChanged = true; - AcquiredReference = current.TryGet(out TestVariableBehaviour, NetworkManager); - } - - [Rpc(SendTo.Everyone)] - public void SendReferenceRpc(NetworkBehaviourReference value) - { - AcquiredReference = value.TryGet(out RpcReceivedBehaviour, NetworkManager); - ReceivedRPC = true; - } - } #endregion } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs index 1fbce803fc..699f4a21f9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs @@ -1,7 +1,7 @@ using System; using System.Collections; +using System.Collections.Generic; using NUnit.Framework; -using Unity.Collections; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; @@ -15,415 +15,154 @@ namespace Unity.Netcode.RuntimeTests /// - Deserializing NetworkObjectReference to NetworkObject /// - Implicit operators of NetworkObjectReference /// - internal class NetworkObjectReferenceTests : IDisposable + internal class NetworkObjectReferenceTests : BaseReferenceTests { - [OneTimeSetUp] - public void OneTimeSetup() - { - // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. - NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); - } - - private class TestNetworkBehaviour : NetworkBehaviour - { - public static bool ReceivedRPC; - - public NetworkVariable TestVariable = new NetworkVariable(); - - public NetworkObject RpcReceivedNetworkObject; - - public GameObject RpcReceivedGameObject; - - [ServerRpc] - public void SendReferenceServerRpc(NetworkObjectReference value) - { - ReceivedRPC = true; - RpcReceivedGameObject = value; - RpcReceivedNetworkObject = value; - } - } - - [Test] - public void TestSerializeNetworkObject() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); - var outWriter = new FastBufferWriter(1300, Allocator.Temp); - try - { - // serialize - var outSerializer = new BufferSerializer(new BufferSerializerWriter(outWriter)); - NetworkObjectReference outReference = networkObjectContext.Object; - outReference.NetworkSerialize(outSerializer); - - // deserialize - NetworkObjectReference inReference = default; - var inReader = new FastBufferReader(outWriter, Allocator.Temp); - try - { - var inSerializer = - new BufferSerializer(new BufferSerializerReader(inReader)); - inReference.NetworkSerialize(inSerializer); - } - finally - { - inReader.Dispose(); - } - - // validate - Assert.NotNull((NetworkObject)inReference); - Assert.AreEqual(inReference.NetworkObjectId, networkObjectContext.Object.NetworkObjectId); - Assert.AreEqual(outReference, inReference); - Assert.AreEqual(networkObjectContext.Object, (NetworkObject)inReference); - } - finally - { - outWriter.Dispose(); - } - } - - [Test] - public void TestSerializeGameObject() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); - var outWriter = new FastBufferWriter(1300, Allocator.Temp); - try - { - // serialize - var outSerializer = new BufferSerializer(new BufferSerializerWriter(outWriter)); - NetworkObjectReference outReference = networkObjectContext.Object.gameObject; - outReference.NetworkSerialize(outSerializer); - - // deserialize - NetworkObjectReference inReference = default; - var inReader = new FastBufferReader(outWriter, Allocator.Temp); - try - { - var inSerializer = - new BufferSerializer(new BufferSerializerReader(inReader)); - inReference.NetworkSerialize(inSerializer); - } - finally - { - inReader.Dispose(); - } - GameObject gameObject = inReference; - - // validate - Assert.AreEqual(outReference, inReference); - Assert.AreEqual(networkObjectContext.Object.gameObject, gameObject); - } - finally - { - outWriter.Dispose(); - } - } - - [Test] - public void TestImplicitConversionToGameObject() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); + protected override int NumberOfClients => 1; - NetworkObjectReference outReference = networkObjectContext.Object.gameObject; - GameObject go = outReference; - Assert.AreEqual(networkObjectContext.Object.gameObject, go); - } - - [Test] - public void TestImplicitToGameObjectIsNullWhenNotFound() + public NetworkObjectReferenceTests(HostOrServer hostOrServer) : base(hostOrServer) { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); - - NetworkObjectReference outReference = networkObjectContext.Object.gameObject; - - networkObjectContext.Object.Despawn(); - Object.DestroyImmediate(networkObjectContext.Object.gameObject); - - GameObject go = outReference; - Assert.IsNull(go); - } - [Test] - public void TestTryGet() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); - - NetworkObjectReference networkObjectReference = networkObjectContext.Object; - - Assert.True(networkObjectReference.TryGet(out NetworkObject networkObject)); - Assert.NotNull(networkObject); - networkObjectReference.TryGet(out NetworkObject result); - Assert.AreEqual(networkObject, result); } - public enum NetworkObjectConstructorTypes + protected override void OnOneTimeSetup() { - None, - NullNetworkObject, - NullGameObject + // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. + NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); + base.OnOneTimeSetup(); } [UnityTest] - public IEnumerator TestSerializeNull([Values] NetworkObjectConstructorTypes networkObjectConstructorTypes) + public IEnumerator TestSerializeNetworkObject() { - TestNetworkBehaviour.ReceivedRPC = false; - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + yield return SpawnTestPrefabInstance(); - switch (networkObjectConstructorTypes) - { - case NetworkObjectConstructorTypes.None: - { - testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference()); - break; - } - case NetworkObjectConstructorTypes.NullNetworkObject: - { - testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference((NetworkObject)null)); - break; - } - case NetworkObjectConstructorTypes.NullGameObject: - { - testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference((GameObject)null)); - break; - } - } + // Explicitly send the NetworkObject as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(new NetworkObjectReference(m_ReferenceToUse.NetworkObject)); + // Validated the reference + yield return WaitForConditionOrTimeOut(NetworkObjectSerializedValidation); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][Explicit reference] Failed to validate {nameof(NetworkObjectReference)} serialization!"); - // wait for rpc completion - float t = 0; - while (!TestNetworkBehaviour.ReceivedRPC) - { + // Implicitly send the NetworkObject as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(m_ReferenceToUse.NetworkObject); - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } + // Validated the reference + yield return WaitForConditionOrTimeOut(NetworkObjectSerializedValidation); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][Implicit reference] Failed to validate {nameof(NetworkObjectReference)} serialization!"); - yield return null; - } + // Use the GameObject to set the NetworkObjet reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(new NetworkObjectReference(m_ReferenceToUse.GameObject)); - // validate - Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedNetworkObject); - Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedGameObject); + // Validated the reference + yield return WaitForConditionOrTimeOut(NetworkObjectSerializedValidation); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][GameObject reference] Failed to validate {nameof(NetworkObjectReference)} serialization!"); } [UnityTest] - public IEnumerator TestRpc() + public IEnumerator TestSerializeNull() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + yield return SpawnTestPrefabInstance(true); - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Initialize with NULL parameter + var initializeWithNullGameObject = new NetworkObjectReference((GameObject)null); + var initializeWithNullNetworkObject = new NetworkObjectReference((NetworkObject)null); + // Initialize with no parameter + var initializeWithNothing = new NetworkObjectReference(); - testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference(otherObjectContext.Object)); + // Initialize with NULL GameObject parameter + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(initializeWithNullGameObject); - // wait for rpc completion - float t = 0; - while (testNetworkBehaviour.RpcReceivedGameObject == null) - { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } + // Validated the reference + yield return WaitForConditionOrTimeOut(SerializingNullNetworkObjectValidated); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][GameObject as NULL] Failed to validate {nameof(NetworkObjectReference)} serialization!"); - yield return null; + // Reset the RPC NetworkObjectReference to the local instance for all spawned instances. + foreach (var networkManager in m_NetworkManagers) + { + var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_ValidatingInstance.NetworkObject.NetworkObjectId].GetComponent(); + testBehaviour.RpcReceivedNetworkObject = testBehaviour.NetworkObject; } - // validate - Assert.AreEqual(otherObjectContext.Object, testNetworkBehaviour.RpcReceivedNetworkObject); - Assert.AreEqual(otherObjectContext.Object.gameObject, testNetworkBehaviour.RpcReceivedGameObject); - } - - [UnityTest] - public IEnumerator TestRpcImplicitNetworkObject() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); - - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Initialize with NULL NetworkObject parameter + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(initializeWithNullNetworkObject); - testNetworkBehaviour.SendReferenceServerRpc(otherObjectContext.Object); + // Validated the reference + yield return WaitForConditionOrTimeOut(SerializingNullNetworkObjectValidated); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][NetworkObject as NULL] Failed to validate {nameof(NetworkObjectReference)} serialization!"); - // wait for rpc completion - float t = 0; - while (testNetworkBehaviour.RpcReceivedGameObject == null) + // Reset the RPC NetworkObjectReference to the local instance for all spawned instances. + foreach (var networkManager in m_NetworkManagers) { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } - - yield return null; + var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_ValidatingInstance.NetworkObject.NetworkObjectId].GetComponent(); + testBehaviour.RpcReceivedNetworkObject = testBehaviour.NetworkObject; } - // validate - Assert.AreEqual(otherObjectContext.Object, testNetworkBehaviour.RpcReceivedNetworkObject); - Assert.AreEqual(otherObjectContext.Object.gameObject, testNetworkBehaviour.RpcReceivedGameObject); + // Initialize with no parameter + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(initializeWithNothing); + + // Validated the reference + yield return WaitForConditionOrTimeOut(SerializingNullNetworkObjectValidated); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][No Parameter] Failed to validate {nameof(NetworkObjectReference)} serialization!"); } [UnityTest] - public IEnumerator TestRpcImplicitGameObject() + public IEnumerator TestGetReferenceAndConversion() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + yield return SpawnTestPrefabInstance(); - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + var referenceToUse = new NetworkObjectReference(m_ReferenceToUse.NetworkObject); - testNetworkBehaviour.SendReferenceServerRpc(otherObjectContext.Object.gameObject); - - // wait for rpc completion - float t = 0; - while (testNetworkBehaviour.RpcReceivedGameObject == null) - { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } + Assert.True(referenceToUse.TryGet(out NetworkObject networkObject)); + Assert.NotNull(networkObject, $"TryGet succeeded but value returned is null!"); - yield return null; - } - - // validate - Assert.AreEqual(otherObjectContext.Object, testNetworkBehaviour.RpcReceivedNetworkObject); - Assert.AreEqual(otherObjectContext.Object.gameObject, testNetworkBehaviour.RpcReceivedGameObject); - } - - [Test] - public void TestNetworkVariable() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + // TODO: Revisit this to determine if this portion of the test is actually needed + referenceToUse.TryGet(out NetworkObject result); + Assert.AreEqual(networkObject, result); - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Now implicitly convert from a NetworkObjectReference to a GameObject + GameObject fromReference = referenceToUse; + Assert.IsTrue(fromReference == m_ReferenceToUse.GameObject, $"Implicitly converting {nameof(NetworkObjectReference)} to {nameof(GameObject)} failed!"); - // check default value is null - Assert.IsNull((NetworkObject)testNetworkBehaviour.TestVariable.Value); + // Despawn + m_ReferenceToUse.NetworkObject.Despawn(); + var referenceName = m_ReferenceToUse.GameObject.name; + yield return WaitForDespawnedOnAllOrTimeOut(new List() { m_ReferenceToUse.NetworkObject }); + AssertOnTimeout($"Timed out waiting for {referenceName} to de-spawn!"); - testNetworkBehaviour.TestVariable.Value = networkObjectContext.Object; + // Destroy + Object.DestroyImmediate(m_ReferenceToUse.GameObject); - Assert.AreEqual((GameObject)testNetworkBehaviour.TestVariable.Value, networkObjectContext.Object.gameObject); - Assert.AreEqual((NetworkObject)testNetworkBehaviour.TestVariable.Value, networkObjectContext.Object); + // Validate trying to implicitly convert returns a null value when the GameObject + // no longer exists + fromReference = referenceToUse; + Assert.IsTrue(fromReference == null, $"Implicitly converting {nameof(NetworkObjectReference)} to {nameof(GameObject)} failed when destroyed!"); } - [Test] - public void TestDespawn() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); - var originalId = networkObjectContext.Object.NetworkObjectId; - - NetworkObjectReference networkObjectReference = networkObjectContext.Object; - Assert.AreEqual(networkObjectContext.Object, (NetworkObject)networkObjectReference); - - networkObjectContext.Object.Despawn(); - Assert.IsFalse(networkObjectReference.TryGet(out NetworkObject _)); - networkObjectContext.Object.Spawn(); - - // After spawning again the reference will still no longer work as it still points to the old object - Assert.AreNotEqual(originalId, networkObjectContext.Object.NetworkObjectId); - Assert.IsFalse(networkObjectReference.TryGet(out NetworkObject _)); - - // creating a new reference will make it work again - networkObjectReference = networkObjectContext.Object; - Assert.AreEqual(networkObjectContext.Object, (NetworkObject)networkObjectReference); - } - - [Test] - public void FailSerializeNonSpawnedNetworkObject() + [UnityTest] + public IEnumerator FailSerializeNonSpawnedNetworkObject() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); + yield return s_DefaultWaitForTick; + var instance = Object.Instantiate(m_TestPrefab); + Assert.Throws(() => { - NetworkObjectReference outReference = networkObjectContext.Object; + NetworkObjectReference outReference = instance.GetComponent(); }); } - [Test] - public void FailSerializeGameObjectWithoutNetworkObject() + [UnityTest] + public IEnumerator FailSerializeGameObjectWithoutNetworkObject() { - using var gameObjectContext = UnityObjectContext.CreateGameObject(); + yield return s_DefaultWaitForTick; + var instance = Object.Instantiate(m_TestPrefab); + Assert.Throws(() => { - NetworkObjectReference outReference = gameObjectContext.Object; + NetworkObjectReference outReference = instance; }); } - - public void Dispose() - { - //Stop, shutdown, and destroy - NetworkManagerHelper.ShutdownNetworkManager(); - } - - public NetworkObjectReferenceTests() - { - //Create, instantiate, and host - NetworkManagerHelper.StartNetworkManager(out _); - } - } - - /// - /// Helper method for tests to create and destroy Unity Objects. - /// - /// The type of Object this context incorporates. - internal class UnityObjectContext : UnityObjectContext where T : Object - { - private T m_Object; - - internal UnityObjectContext(T unityObject, Object root) - : base(root) - { - m_Object = unityObject; - } - - public T Object => m_Object; - } - - internal class UnityObjectContext : IDisposable - { - private Object m_Root; - - protected UnityObjectContext(Object root) - { - m_Root = root; - } - - public static UnityObjectContext CreateGameObject(string name = "") - { - var gameObject = new GameObject(name); - return new UnityObjectContext(gameObject, gameObject); - } - - public static UnityObjectContext CreateNetworkObject(string name = "") - { - var gameObject = new GameObject(name); - var networkObject = gameObject.AddComponent(); - return new UnityObjectContext(networkObject, gameObject); - } - - public void Dispose() - { - Object.DestroyImmediate(m_Root); - } } } From 461ca09e5663e28cfaa640606a5f642bb21591ec Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 21 Jul 2026 14:00:33 -0500 Subject: [PATCH 6/7] test - fix Using the fixed version of NetworkPrefabHandlerSpawnAndSynchronizeTests. Fixing some issues with prefab and handler creation. --- ...etworkPrefabHandlerSynchronizationTests.cs | 70 +++++++++++++++---- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs index 09d1e8c7b4..d15522665d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs @@ -19,6 +19,28 @@ public NetworkPrefabHandlerSynchronizationTests(HostOrServer hostOrServer) : bas private GameObject m_ClientSideValidPrefab; private GameObject m_ClientSideExceptionPrefab; + public class VerifyLastClientSentRpcToServer : NetworkBehaviour + { + public bool RpcReceived { get; private set; } + + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + RpcReceived = false; + base.OnNetworkPreSpawn(ref networkManager); + } + + public void DelayUntilOneMessageReceivedRpc(RpcParams rpcParams = default) + { + RpcReceived = true; + } + } + + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + base.OnCreatePlayerPrefab(); + } + protected override void OnServerAndClientsCreated() { m_ValidPrefab = CreateNetworkObjectPrefab("ValidPrefab"); @@ -28,17 +50,22 @@ protected override void OnServerAndClientsCreated() } [UnityTest] - [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer, RuntimePlatform.OSXPlayer, RuntimePlatform.OSXEditor })] // Ignored test tracked in MTT-15473 public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() { var nonAuthority = GetNonAuthorityNetworkManager(); var networkObjectToSpawnOnClient = m_ClientSideValidPrefab.GetComponent(); - nonAuthority.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, new NetworkPrefabExceptionThrower()); - var prefabHandlerObject = new GameObject(); - var prefabHandler = prefabHandlerObject.AddComponent(); - prefabHandler.Initialize(nonAuthority, m_ValidPrefab.GetComponent()); - //nonAuthority.PrefabHandler.AddHandler(m_ValidPrefab, new NetworkPrefabInstanceHandler(networkObjectToSpawnOnClient)); + + var clientSideHandler = new GameObject(); + var clientPrefabHandler = clientSideHandler.AddComponent(); + clientPrefabHandler.Initialize(nonAuthority, m_ClientSideValidPrefab.GetComponent()); + + nonAuthority.PrefabHandler.AddHandler(m_ValidPrefab, clientPrefabHandler); + + var clientSideExceptionHandler = new GameObject(); + var clientSideExceptionPrefabHandler = clientSideExceptionHandler.AddComponent(); + + nonAuthority.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, clientSideExceptionPrefabHandler); var authority = GetAuthorityNetworkManager(); @@ -63,14 +90,16 @@ public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() // Create a new client and register the same PrefabHandlers on the client var newClient = CreateNewClient(); - var prefabHandlerObject2 = new GameObject(); - var prefabHandler2 = prefabHandlerObject2.AddComponent(); - newClient.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, new NetworkPrefabExceptionThrower()); + var lateJoinClientSideHandler = new GameObject(); + var lateJoinClientPrefabHandler = clientSideHandler.AddComponent(); + var lateJoinExceptionHandler = new GameObject(); + var lateJoinClientExceptionHandler = lateJoinExceptionHandler.AddComponent(); - var prefabHandlerObject3 = new GameObject(); - var prefabHandler3 = prefabHandlerObject3.AddComponent(); - prefabHandler3.Initialize(nonAuthority, networkObjectToSpawnOnClient); + lateJoinClientPrefabHandler.Initialize(newClient, m_ClientSideValidPrefab.GetComponent()); + + newClient.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, lateJoinClientExceptionHandler); + newClient.PrefabHandler.AddHandler(m_ValidPrefab, lateJoinClientPrefabHandler); // Expect assertions from the new client LogAssert.Expect(LogType.Exception, "Exception: exception while instantiating"); @@ -82,6 +111,8 @@ public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() // Start and synchronize the new client yield return StartClient(newClient); + AssertOnTimeout($"Timed out waiting for the late joining client, {newClient.name}, to connect!"); + // Validate the valid prefab spawned on all clients without issue var expectedAuthorityHash = m_ValidPrefab.GetComponent().GlobalObjectIdHash; @@ -102,7 +133,20 @@ public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() } } - Object.Destroy(prefabHandlerObject); + // Assure this test continues to run until we verify the late joining client has sent 1 message to the server + // This should be the fix for MTT-15473 where the test finishes/exits before the message from the client has been received and processed by the server. + Assert.IsTrue(authority.SpawnManager.SpawnedObjects.ContainsKey(newClient.LocalClient.PlayerObject.NetworkObjectId), $"Server does not have a player for Client-{newClient.LocalClientId}!"); + + // Get server and late joining client's VerifyLastClientSentRpcToServer NetworkBehaviour + var serverLateClientInstance = authority.SpawnManager.SpawnedObjects[newClient.LocalClient.PlayerObject.NetworkObjectId].GetComponent(); + var sendRpc = newClient.LocalClient.PlayerObject.GetComponent(); + + // Send a message from the late joining client to the server + sendRpc.DelayUntilOneMessageReceivedRpc(); + + // Wait for the server to have received this message before exiting the test. + // If the log message has not been received by the server at this point, then there is some other type of bug specific to iOS and Mac. + yield return WaitForConditionOrTimeOut(() => serverLateClientInstance.RpcReceived); } } } From 07641975f2753a366381d89ebfd4e33f48668b08 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 21 Jul 2026 16:00:41 -0500 Subject: [PATCH 7/7] style Fixing formatting issues. --- .../Tests/Runtime/NetworkBehaviourGenericTests.cs | 7 +++---- .../Prefabs/NetworkPrefabHandlerWithDataTests.cs | 15 ++++++++------- .../Runtime/TestHelpers/NetcodeIntegrationTest.cs | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs index 967e29e0fc..d881cf620b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs @@ -41,10 +41,7 @@ protected override void OnServerAndClientsCreated() m_PrefabToSpawn = CreateNetworkObjectPrefab("TestPrefab"); m_PrefabToSpawn.AddComponent(); - var childObject = new GameObject - { - name = "ChildObject" - }; + var childObject = new GameObject("ChildObject"); childObject.transform.parent = m_PrefabToSpawn.transform; childObject.AddComponent(); base.OnServerAndClientsCreated(); @@ -147,6 +144,7 @@ public IEnumerator ValidateNoSpam() // We make an instance of the m_PrefabToSpawn var validateInstance = Object.Instantiate(m_PrefabToSpawn); + // Then destroy the NetworkObject componwent of that instance. Object.DestroyImmediate(validateInstance.GetComponent()); @@ -161,6 +159,7 @@ public IEnumerator ValidateNoSpam() simpleNetworkBehaviour.IsSpawned = false; simpleNetworkBehaviour = null; + // Destroy this test instance Object.DestroyImmediate(validateInstance); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs index 76d6467a04..c4bcd3f124 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs @@ -5,6 +5,7 @@ using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; +using Object = UnityEngine.Object; namespace Unity.Netcode.RuntimeTests { @@ -73,8 +74,6 @@ public IEnumerator InstantiationPayload_LateJoinersReceiveData() yield return WaitForSpawnedOnAllOrTimeOut(spawned); AssertOnTimeout($"Not all clients spawned {spawned.name}!"); - - // When running with Distributed Authority, test a late-joiner after an ownership change // The object owner will synchronize the late joining client, showing that the instantiationData will survive host migration. if (m_DistributedAuthority) @@ -111,8 +110,8 @@ private void RegisterPrefabHandler(NetworkManager manager, out PrefabInstanceHan private NetworkObject SpawnPrefabWithData(NetworkSerializableTest data) { var authority = GetAuthorityNetworkManager(); - var instance = UnityEngine.Object.Instantiate(m_Prefab).GetComponent(); - + var instance = Object.Instantiate(m_Prefab).GetComponent(); + GetAuthorityNetworkManager().PrefabHandler.SetInstantiationData(instance, data); SpawnObjectInstance(instance, authority); @@ -137,12 +136,12 @@ public PrefabInstanceHandlerWithData(GameObject prefab) public override NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation, NetworkSerializableTest data) { InstantiationData = data; - return UnityEngine.Object.Instantiate(m_Prefab, position, rotation).GetComponent(); + return Object.Instantiate(m_Prefab, position, rotation).GetComponent(); } public override void Destroy(NetworkObject networkObject) { - UnityEngine.Object.DestroyImmediate(networkObject.gameObject); + Object.DestroyImmediate(networkObject.gameObject); } } @@ -158,7 +157,9 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade } public bool IsSynchronizedWith(NetworkSerializableTest other) - => Value == other.Value && Math.Abs(Value2 - other.Value2) < 0.0001f; + { + return Value == other.Value && Math.Abs(Value2 - other.Value2) < 0.0001f; + } } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index 722b009912..5b2f5a986b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -794,7 +794,7 @@ protected void CreateServerAndClients(int numberOfClients) { CreatePlayerPrefab(); } - + if (m_EnableTimeTravel) {