From 7ffbe93a125924fdbb85ac0d966083bfe6baee05 Mon Sep 17 00:00:00 2001 From: Emma Date: Wed, 18 Jun 2025 14:13:33 -0400 Subject: [PATCH 01/10] fix/mttb-1377/synchronize-despawn-to-client --- .../Connection/NetworkConnectionManager.cs | 45 +--- .../Messages/DestroyObjectMessage.cs | 47 ++-- .../Runtime/Spawning/NetworkSpawnManager.cs | 89 ++++--- .../DeferredDespawningTests.cs | 17 +- .../NetworkObjectOnNetworkDespawnTests.cs | 31 +-- .../NetworkObjectOnSpawnTests.cs | 98 +++---- .../Tests/Runtime/NetworkShowHideTests.cs | 242 ++++++++++-------- .../TestHelpers/NetcodeIntegrationTest.cs | 65 ++++- .../InSceneParentChildHandler.cs | 2 +- .../ParentingAutoSyncManager.cs | 8 +- .../InScenePlacedNetworkObjectDestroyTests.cs | 218 ++++++++++++++++ ...enePlacedNetworkObjectDestroyTests.cs.meta | 3 + .../InScenePlacedNetworkObjectTests.cs | 115 +-------- .../NetworkObjectTestComponent.cs | 4 + .../ParentingInSceneObjectsTests.cs | 137 +++------- 15 files changed, 612 insertions(+), 509 deletions(-) create mode 100644 testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs create mode 100644 testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index ade21e09ce..1fb7eaed6f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -7,6 +7,7 @@ using Unity.Collections.LowLevel.Unsafe; using Unity.Profiling; using UnityEngine; +using Object = UnityEngine.Object; namespace Unity.Netcode { @@ -1126,36 +1127,15 @@ internal void OnClientDisconnectFromServer(ulong clientId) { if (!playerObject.DontDestroyWithOwner) { - // If a player NetworkObject is being despawned, make sure to remove all children if they are marked to not be destroyed - // with the owner. - if (NetworkManager.DAHost) - { - // Remove any children from the player object if they are not going to be destroyed with the owner - var childNetworkObjects = playerObject.GetComponentsInChildren(); - foreach (var child in childNetworkObjects) - { - // TODO: We have always just removed all children, but we might think about changing this to preserve the nested child hierarchy. - if (child.DontDestroyWithOwner && child.transform.transform.parent) - { - // If we are here, then we are running in DAHost mode and have the authority to remove the child from its parent - child.AuthorityAppliedParenting = true; - child.TryRemoveParentCachedWorldPositionStays(); - } - } - } - - if (NetworkManager.PrefabHandler.ContainsHandler(playerObject.GlobalObjectIdHash)) - { - // Despawn but don't destroy. DA Host will act like the service and send despawn notifications. - NetworkManager.SpawnManager.DespawnObject(playerObject, false, NetworkManager.DistributedAuthorityMode); - // Let the prefab handler determine if it will be destroyed - NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(playerObject); - } - else if (playerObject.IsSpawned) + if (playerObject.IsSpawned) { // Call despawn to assure NetworkBehaviour.OnNetworkDespawn is invoked on the server-side (when the client side disconnected). // This prevents the issue (when just destroying the GameObject) where any NetworkBehaviour component(s) destroyed before the NetworkObject would not have OnNetworkDespawn invoked. - NetworkManager.SpawnManager.DespawnObject(playerObject, true, NetworkManager.DistributedAuthorityMode); + NetworkManager.SpawnManager.DespawnObject(playerObject, true, true); + } + else + { + Object.Destroy(playerObject.gameObject); } } else if (!NetworkManager.ShutdownInProgress) @@ -1180,18 +1160,13 @@ internal void OnClientDisconnectFromServer(ulong clientId) // Handle an object with no observers other than the current disconnecting client as destroying with owner if (!ownedObject.DontDestroyWithOwner && (ownedObject.Observers.Count == 0 || (ownedObject.Observers.Count == 1 && ownedObject.Observers.Contains(clientId)))) { - if (NetworkManager.PrefabHandler.ContainsHandler(ownedObject.GlobalObjectIdHash)) + if (ownedObject.IsSpawned) { - if (ownedObject.IsSpawned) - { - // Don't destroy (prefab handler will determine this, but always notify - NetworkManager.SpawnManager.DespawnObject(ownedObject, false, true); - } - NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(ownedObject); + NetworkManager.SpawnManager.DespawnObject(ownedObject, true, true); } else { - NetworkManager.SpawnManager.DespawnObject(ownedObject, true, true); + Object.Destroy(ownedObject.gameObject); } } else if (!NetworkManager.ShutdownInProgress) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index f519a11827..4268eb59d3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -1,22 +1,19 @@ using System.Linq; using System.Runtime.CompilerServices; +using UnityEngine; namespace Unity.Netcode { internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy { private const int k_OptimizeDestroyObjectMessage = 1; - public int Version => k_OptimizeDestroyObjectMessage; + private const int k_AllowDestroyGameInPlaced = 2; + public int Version => k_AllowDestroyGameInPlaced; private const string k_Name = "DestroyObjectMessage"; public ulong NetworkObjectId; - /// - /// Used to communicate whether to destroy the associated game object. - /// Should be false if the object is InScenePlaced and true otherwise - /// - public bool DestroyGameObject; private byte m_DestroyFlags; internal int DeferredDespawnTick; @@ -25,32 +22,29 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp internal bool IsDistributedAuthority; - private const byte k_ClientTargetedDestroy = 0x01; - private const byte k_DeferredDespawn = 0x02; - internal bool IsTargetedDestroy { - get => GetFlag(k_ClientTargetedDestroy); + get => ByteUtility.GetBit(m_DestroyFlags, 0); - set => SetFlag(value, k_ClientTargetedDestroy); + set => ByteUtility.SetBit(ref m_DestroyFlags, 0, value); } private bool IsDeferredDespawn { - get => GetFlag(k_DeferredDespawn); + get => ByteUtility.GetBit(m_DestroyFlags, 1); - set => SetFlag(value, k_DeferredDespawn); + set => ByteUtility.SetBit(ref m_DestroyFlags, 1, value); } - private bool GetFlag(int flag) + /// + /// Used to communicate whether to destroy the associated game object. + /// Should be false if the object is InScenePlaced and true otherwise + /// + public bool DestroyGameObject { - return (m_DestroyFlags & flag) != 0; - } + get => ByteUtility.GetBit(m_DestroyFlags, 2); - private void SetFlag(bool set, byte flag) - { - if (set) { m_DestroyFlags = (byte)(m_DestroyFlags | flag); } - else { m_DestroyFlags = (byte)(m_DestroyFlags & ~flag); } + set => ByteUtility.SetBit(ref m_DestroyFlags, 2, value); } public void Serialize(FastBufferWriter writer, int targetVersion) @@ -73,6 +67,9 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { BytePacker.WriteValueBitPacked(writer, DeferredDespawnTick); } + } else if (targetVersion >= k_AllowDestroyGameInPlaced) + { + writer.WriteByteSafe(m_DestroyFlags); } if (targetVersion < k_OptimizeDestroyObjectMessage) @@ -102,11 +99,15 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int { ByteUnpacker.ReadValueBitPacked(reader, out DeferredDespawnTick); } + } else if (receivedMessageVersion >= k_AllowDestroyGameInPlaced) + { + reader.ReadByteSafe(out m_DestroyFlags); } if (receivedMessageVersion < k_OptimizeDestroyObjectMessage) { - reader.ReadValueSafe(out DestroyGameObject); + reader.ReadValueSafe(out bool destroyGameObject); + DestroyGameObject = destroyGameObject; } if (networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) @@ -169,7 +170,7 @@ public void Handle(ref NetworkContext context) } // Otherwise just despawn the NetworkObject right now - networkManager.SpawnManager.OnDespawnNonAuthorityObject(networkObject); + networkManager.SpawnManager.OnDespawnNonAuthorityObject(networkObject, DestroyGameObject); networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize); } @@ -208,7 +209,7 @@ private void HandleDeferredDespawn(ref NetworkManager networkManager, ref Networ { networkObject.DeferredDespawnTick = DeferredDespawnTick; var hasCallback = networkObject.OnDeferredDespawnComplete != null; - networkManager.SpawnManager.DeferDespawnNetworkObject(NetworkObjectId, DeferredDespawnTick, hasCallback); + networkManager.SpawnManager.DeferDespawnNetworkObject(NetworkObjectId, DeferredDespawnTick, hasCallback, DestroyGameObject); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 8a39fcfa06..030c458655 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -18,7 +18,9 @@ public class NetworkSpawnManager internal Dictionary> ClientsToShowObject = new Dictionary>(); /// - /// The currently spawned objects + /// Dictionary of currently spawned network objects. + /// A table of: + /// [][] /// public readonly Dictionary SpawnedObjects = new Dictionary(); @@ -1366,7 +1368,7 @@ internal void SendSpawnCallForObserverUpdate(ulong[] newObservers, NetworkObject return parentNetworkObject.NetworkObjectId; } - internal void DespawnObject(NetworkObject networkObject, bool destroyObject = false, bool playerDisconnect = false) + internal void DespawnObject(NetworkObject networkObject, bool destroyObject = false, bool authorityOverride = false) { if (!networkObject.IsSpawned) { @@ -1382,13 +1384,13 @@ internal void DespawnObject(NetworkObject networkObject, bool destroyObject = fa if (NetworkManager.DistributedAuthorityMode && networkObject.OwnerClientId != NetworkManager.LocalClientId) { - if (!NetworkManager.DAHost || NetworkManager.DAHost && !playerDisconnect) + if (!NetworkManager.DAHost || NetworkManager.DAHost && !authorityOverride) { NetworkLog.LogErrorServer($"In distributed authority mode, only the owner of the NetworkObject can despawn it! Local Client is ({NetworkManager.LocalClientId}) while the owner is ({networkObject.OwnerClientId})"); return; } } - OnDespawnObject(networkObject, destroyObject, playerDisconnect); + OnDespawnObject(networkObject, destroyObject, authorityOverride); } // Makes scene objects ready to be reused @@ -1569,21 +1571,33 @@ internal void ServerSpawnSceneObjectsOnStartSweep() } /// + /// Non Authority Side: /// Called when destroying an object after receiving a . /// Processes logic for how to destroy objects on the non-authority client. /// - internal void OnDespawnNonAuthorityObject([NotNull] NetworkObject networkObject) + internal void OnDespawnNonAuthorityObject([NotNull] NetworkObject networkObject, bool destroyGameObject) { if (networkObject.HasAuthority) { NetworkLog.LogError($"OnDespawnNonAuthorityObject called on object {networkObject.NetworkObjectId} when is current client {NetworkManager.LocalClientId} has authority on this object."); } - // On the non-authority, never destroy the game object when InScenePlaced, otherwise always destroy on non-authority side - OnDespawnObject(networkObject, networkObject.IsSceneObject == false); + if (networkObject.IsSceneObject == false) + { + // If the object is not an in-scene placed NetworkObject, then we always destroy the object on the non-authority side + destroyGameObject = true; + } + + OnDespawnObject(networkObject, destroyGameObject); } - internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject, bool modeDestroy = false) + /// + /// Handles despawning a network object + /// + /// The to despawn. + /// Whether to destroy the underlying game object, or to simply despawn the object. + /// Gives the DAHost server permissions. Otherwise, DAHost only has authority on objects it owns. + internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject, bool authorityOverride = false) { if (!NetworkManager) { @@ -1593,7 +1607,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec // We have to do this check first as subsequent checks assume we can access NetworkObjectId. if (!networkObject) { - Debug.LogWarning($"Trying to destroy network object but it is null"); + Debug.LogWarning("Trying to destroy network object but it is null"); return; } @@ -1607,10 +1621,19 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec return; } + if (destroyGameObject && networkObject.IsSceneObject == true && NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + Debug.LogWarning("Destroying in-scene network objects can lead to unexpected behavior. It is recommended to use NetworkObject.Despawn(false) instead."); + } + + var distributedAuthority = NetworkManager.DistributedAuthorityMode; + var hasDAAuthority = distributedAuthority && (networkObject.HasAuthority || (NetworkManager.DAHost && authorityOverride)); + var hasClientServerAuthority = !distributedAuthority && NetworkManager.IsServer; + var hasAuthority = hasDAAuthority || hasClientServerAuthority; + // If we are shutting down the NetworkManager, then ignore resetting the parent // and only attempt to remove the child's parent on the server-side - var distributedAuthority = NetworkManager.DistributedAuthorityMode; - if (!NetworkManager.ShutdownInProgress && (NetworkManager.IsServer || distributedAuthority)) + if (!NetworkManager.ShutdownInProgress && hasAuthority) { // Get all child NetworkObjects var objectsToRemoveParent = networkObject.GetComponentsInChildren(); @@ -1630,9 +1653,8 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec continue; } // For mixed authority hierarchies, if the parent is despawned then any removal of children - // is considered "authority approved". If we don't have authority over the object and we are - // in distributed authority mode, then set the AuthorityAppliedParenting flag. - spawnedNetObj.AuthorityAppliedParenting = distributedAuthority && !spawnedNetObj.HasAuthority; + // is considered "authority approved". Set the AuthorityAppliedParenting flag. + spawnedNetObj.AuthorityAppliedParenting = authorityOverride; // Try to remove the parent using the cached WorldPositionStays value // Note: WorldPositionStays will still default to true if this was an @@ -1655,9 +1677,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec networkObject.InvokeBehaviourNetworkDespawn(); - if (NetworkManager != null && ((NetworkManager.IsServer && (!distributedAuthority || - (distributedAuthority && modeDestroy))) || - (distributedAuthority && networkObject.OwnerClientId == NetworkManager.LocalClientId))) + if (!NetworkManager.ShutdownInProgress && hasAuthority) { if (NetworkManager.NetworkConfig.RecycleNetworkIds) { @@ -1669,14 +1689,17 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } m_TargetClientIds.Clear(); - // If clients are not allowed to spawn locally then go ahead and send the despawn message or if we are in distributed authority mode, we are the server, we own this NetworkObject - // send the despawn message, and as long as we have any remaining clients, then notify of the object being destroy. - if (NetworkManager.IsServer && NetworkManager.ConnectedClientsList.Count > 0 && (!distributedAuthority || - (NetworkManager.DAHost && distributedAuthority && - (networkObject.OwnerClientId == NetworkManager.LocalClientId || modeDestroy)))) + /* + * Configure message targets + */ + + // If we are using distributed authority and are not the DAHost, send a message to the Server (CMBService or DAHost) + if (hasDAAuthority && !NetworkManager.DAHost) { - // We keep only the client for which the object is visible - // as the other clients have them already despawned + m_TargetClientIds.Add(NetworkManager.ServerClientId); + } + // Otherwise send to the clients for which the object is visible + else { foreach (var clientId in NetworkManager.ConnectedClientsIds) { if ((distributedAuthority && clientId == networkObject.OwnerClientId) || clientId == NetworkManager.LocalClientId) @@ -1689,17 +1712,9 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } } } - else // DANGO-TODO: If we are not the server, distributed authority mode is enabled, and we are the owner then inform the DAHost to despawn the NetworkObject - if (!NetworkManager.IsServer && distributedAuthority && networkObject.OwnerClientId == NetworkManager.LocalClientId) - { - // DANGO-TODO: If a shutdown is not in progress or a shutdown is in progress and we can destroy with the owner then notify the DAHost - if (!NetworkManager.ShutdownInProgress || (NetworkManager.ShutdownInProgress && !networkObject.DontDestroyWithOwner)) - { - m_TargetClientIds.Add(NetworkManager.ServerClientId); - } - } - if (m_TargetClientIds.Count > 0 && !NetworkManager.ShutdownInProgress) + // If we have any targets, create and send the message + if (m_TargetClientIds.Count > 0) { var message = new DestroyObjectMessage { @@ -2086,6 +2101,7 @@ internal struct DeferredDespawnObject { public int TickToDespawn; public bool HasDeferredDespawnCheck; + public bool DestroyGameObject; public ulong NetworkObjectId; } @@ -2097,12 +2113,13 @@ internal struct DeferredDespawnObject /// associated NetworkObject /// when to despawn the NetworkObject /// if true, user script is to be invoked to determine when to despawn - internal void DeferDespawnNetworkObject(ulong networkObjectId, int tickToDespawn, bool hasDeferredDespawnCheck) + internal void DeferDespawnNetworkObject(ulong networkObjectId, int tickToDespawn, bool hasDeferredDespawnCheck, bool destroyGameObject) { var deferredDespawnObject = new DeferredDespawnObject() { TickToDespawn = tickToDespawn, HasDeferredDespawnCheck = hasDeferredDespawnCheck, + DestroyGameObject = destroyGameObject, NetworkObjectId = networkObjectId, }; DeferredDespawnObjects.Add(deferredDespawnObject); @@ -2169,7 +2186,7 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime) if (SpawnedObjects.TryGetValue(deferredObjectEntry.NetworkObjectId, out var networkObject)) { // Local instance despawns the instance - OnDespawnNonAuthorityObject(networkObject); + OnDespawnNonAuthorityObject(networkObject, deferredObjectEntry.DestroyGameObject); } DeferredDespawnObjects.RemoveAt(i); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs index fdfe2402be..1902539113 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs @@ -18,12 +18,6 @@ internal class DeferredDespawningTests : IntegrationTestWithApproximation private List m_DaisyChainedDespawnObjects = new List(); private List m_HasReachedEnd = new List(); - // TODO: [CmbServiceTests] Adapt to run with the service - protected override bool UseCMBService() - { - return false; - } - public enum SetDestroyGameObject { DestroyGameObject, @@ -67,7 +61,7 @@ public IEnumerator DeferredDespawning() DeferredDespawnDaisyChained.ClientRelativeInstances = new Dictionary>(); // Spawn the initial object - var rootInstance = SpawnObject(m_DaisyChainedDespawnObjects[0], m_ServerNetworkManager); + var rootInstance = SpawnObject(m_DaisyChainedDespawnObjects[0], GetAuthorityNetworkManager()); DeferredDespawnDaisyChained.ReachedLastChainInstance = ReachedLastChainObject; // Wait for the chain of objects to spawn and despawn @@ -87,14 +81,9 @@ public IEnumerator DeferredDespawning() private bool HaveAllClientsReachedEndOfChain() { - if (!m_HasReachedEnd.Contains(m_ServerNetworkManager.LocalClientId)) - { - return false; - } - - foreach (var client in m_ClientNetworkManagers) + foreach (var manager in m_NetworkManagers) { - if (!m_HasReachedEnd.Contains(client.LocalClientId)) + if (!m_HasReachedEnd.Contains(manager.LocalClientId)) { return false; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs index 1d1b6c518c..0c51439032 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs @@ -26,17 +26,8 @@ public enum InstanceTypes private GameObject m_ObjectToSpawn; private NetworkObject m_NetworkObject; - private HostOrServer m_HostOrServer; - - // TODO: [CmbServiceTests] Adapt to run with the service - protected override bool UseCMBService() - { - return false; - } - public NetworkObjectOnNetworkDespawnTests(HostOrServer hostOrServer) : base(hostOrServer) { - m_HostOrServer = hostOrServer; } internal class OnNetworkDespawnTestComponent : NetworkBehaviour @@ -65,22 +56,13 @@ protected override void OnServerAndClientsCreated() private bool ObjectSpawnedOnAllNetworkManagerInstances() { - if (!s_GlobalNetworkObjects.ContainsKey(m_ServerNetworkManager.LocalClientId)) - { - return false; - } - if (!s_GlobalNetworkObjects[m_ServerNetworkManager.LocalClientId].ContainsKey(m_NetworkObject.NetworkObjectId)) + foreach (var manager in m_NetworkManagers) { - return false; - } - - foreach (var clientNetworkManager in m_ClientNetworkManagers) - { - if (!s_GlobalNetworkObjects.ContainsKey(clientNetworkManager.LocalClientId)) + if (!s_GlobalNetworkObjects.ContainsKey(manager.LocalClientId)) { return false; } - if (!s_GlobalNetworkObjects[clientNetworkManager.LocalClientId].ContainsKey(m_NetworkObject.NetworkObjectId)) + if (!s_GlobalNetworkObjects[manager.LocalClientId].ContainsKey(m_NetworkObject.NetworkObjectId)) { return false; } @@ -96,8 +78,11 @@ private bool ObjectSpawnedOnAllNetworkManagerInstances() [UnityTest] public IEnumerator TestNetworkObjectDespawnOnShutdown([Values(InstanceTypes.Server, InstanceTypes.Client)] InstanceTypes despawnCheck) { - var networkManager = despawnCheck == InstanceTypes.Server ? m_ServerNetworkManager : m_ClientNetworkManagers[0]; - var networkManagerOwner = m_ServerNetworkManager; + var authority = GetAuthorityNetworkManager(); + var nonAuthority = GetNonAuthorityNetworkManager(); + + var networkManager = despawnCheck == InstanceTypes.Server ? authority : nonAuthority; + var networkManagerOwner = authority; if (m_DistributedAuthority) { networkManagerOwner = networkManager; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs index 413ee87c39..cfe9ef72e3 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.Text; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; @@ -17,12 +18,6 @@ internal class NetworkObjectOnSpawnTests : NetcodeIntegrationTest protected override int NumberOfClients => 2; - // TODO: [CmbServiceTests] Adapt to run with the service - protected override bool UseCMBService() - { - return false; - } - public enum ObserverTestTypes { WithObservers, @@ -44,65 +39,63 @@ protected override void OnServerAndClientsCreated() base.OnServerAndClientsCreated(); } - private bool CheckClientsSideObserverTestObj() + private bool CheckClientsSideObserverTestObj(StringBuilder errorMessage) { foreach (var client in m_ClientNetworkManagers) { + if (client.LocalClient.IsSessionOwner) + { + continue; + } + + var hasObjects = s_GlobalNetworkObjects.TryGetValue(client.LocalClientId, out var clientObjects); if (m_ObserverTestType == ObserverTestTypes.WithObservers) { // When validating this portion of the test and spawning with observers is true, there // should be spawned objects on the clients. - if (!s_GlobalNetworkObjects.ContainsKey(client.LocalClientId)) + if (!hasObjects) { + errorMessage.AppendLine($"[Client-{client.LocalClientId}] has an no objects in global network object list"); return false; } - } - else - { - // When validating this portion of the test and spawning with observers is false, there - // should be no spawned objects on the clients. - if (s_GlobalNetworkObjects.ContainsKey(client.LocalClientId)) + + // Make sure they did spawn the object + if (!clientObjects.TryGetValue(m_ObserverTestNetworkObject.NetworkObjectId, out var clientObject)) { + errorMessage.AppendLine($"[Client-{client.LocalClientId}] has no reference to object {m_ObserverTestNetworkObject.NetworkObjectId}"); return false; } - // We don't need to check anything else for spawn without observers - continue; - } - - var clientObjects = s_GlobalNetworkObjects[client.LocalClientId]; - // Make sure they did spawn the object - if (m_ObserverTestType == ObserverTestTypes.WithObservers) - { - if (!clientObjects.ContainsKey(m_ObserverTestNetworkObject.NetworkObjectId)) + if (!clientObject.IsSpawned) { + errorMessage.AppendLine($"[Client-{client.LocalClientId}] has not spawned object {m_ObserverTestNetworkObject.NetworkObjectId}"); return false; } - if (!clientObjects[m_ObserverTestNetworkObject.NetworkObjectId].IsSpawned) + } + else + { + // When validating this portion of the test and spawning with observers is false, there + // should be no spawned objects on the clients. + if (hasObjects) { + errorMessage.AppendLine($"[Client-{client.LocalClientId}] has an object in global network object"); return false; } + // We don't need to check anything else for spawn without observers } } return true; } - /// - /// Assures the late joining client has all - /// NetworkPrefabs required to connect. - /// - protected override void OnNewClientCreated(NetworkManager networkManager) - { - networkManager.NetworkConfig.EnableSceneManagement = m_ServerNetworkManager.NetworkConfig.EnableSceneManagement; - base.OnNewClientCreated(networkManager); - } - /// /// This test validates property /// /// whether to spawn with or without observers + /// whether or not to use scene management [UnityTest] public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTypes, [Values] SceneManagementState sceneManagement) { + var authority = GetAuthorityNetworkManager(); + if (sceneManagement == SceneManagementState.SceneManagementDisabled) { // When scene management is disabled, we need this wait period for all clients to be up to date with @@ -112,7 +105,7 @@ public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTyp yield return new WaitForSeconds(0.5f); } // Disable prefabs to prevent them from being destroyed - foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs) + foreach (var networkPrefab in authority.NetworkConfig.Prefabs.Prefabs) { networkPrefab.Prefab.SetActive(false); } @@ -120,24 +113,36 @@ public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTyp // Shutdown and clean up the current client NetworkManager instances foreach (var networkManager in m_ClientNetworkManagers) { + if (networkManager == authority) + { + continue; + } + m_PlayerNetworkObjects[networkManager.LocalClientId].Clear(); m_PlayerNetworkObjects.Remove(networkManager.LocalClientId); yield return StopOneClient(networkManager, true); } // Shutdown and clean up the server NetworkManager instance - m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId].Clear(); - yield return StopOneClient(m_ServerNetworkManager); + m_PlayerNetworkObjects[authority.LocalClientId].Clear(); + yield return StopOneClient(authority); // Set the prefabs to active again - foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs) + foreach (var networkPrefab in authority.NetworkConfig.Prefabs.Prefabs) { networkPrefab.Prefab.SetActive(true); } // Disable scene management and start the host - m_ServerNetworkManager.NetworkConfig.EnableSceneManagement = false; - m_ServerNetworkManager.StartHost(); + authority.NetworkConfig.EnableSceneManagement = false; + if (m_UseCmbService) + { + yield return StartClient(authority); + } + else + { + authority.StartHost(); + } yield return s_DefaultWaitForTick; // Create 2 new clients and connect them @@ -150,7 +155,7 @@ public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTyp m_ObserverTestType = observerTestTypes; var prefabNetworkObject = m_ObserverPrefab.GetComponent(); prefabNetworkObject.SpawnWithObservers = observerTestTypes == ObserverTestTypes.WithObservers; - var instance = SpawnObject(m_ObserverPrefab, m_ServerNetworkManager); + var instance = SpawnObject(m_ObserverPrefab, authority); m_ObserverTestNetworkObject = instance.GetComponent(); var withoutObservers = m_ObserverTestType == ObserverTestTypes.WithoutObservers; if (withoutObservers) @@ -166,6 +171,10 @@ public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTyp // Make each client an observer foreach (var client in m_ClientNetworkManagers) { + if (client == authority) + { + continue; + } m_ObserverTestNetworkObject.NetworkShow(client.LocalClientId); } @@ -175,18 +184,19 @@ public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTyp AssertOnTimeout($"{k_WithObserversError} {k_ObserverTestObjName} object!"); // Validate that a late joining client does not see the NetworkObject when it spawns - yield return CreateAndStartNewClient(); + var lateJoinClient = CreateNewClient(); + yield return StartClient(lateJoinClient); m_ObserverTestType = ObserverTestTypes.WithoutObservers; // Just give a little time to make sure nothing spawned yield return s_DefaultWaitForTick; // This just requires a targeted check to assure the newly joined client did not spawn the NetworkObject with SpawnWithObservers set to false - var lateJoinClientId = m_ClientNetworkManagers[m_ClientNetworkManagers.Length - 1].LocalClientId; + var lateJoinClientId = lateJoinClient.LocalClientId; Assert.False(s_GlobalNetworkObjects.ContainsKey(lateJoinClientId), $"[Client-{lateJoinClientId}] Spawned {instance.name} when it shouldn't have!"); // Now validate that we can make the NetworkObject visible to the newly joined client - m_ObserverTestNetworkObject.NetworkShow(m_ClientNetworkManagers[NumberOfClients].LocalClientId); + m_ObserverTestNetworkObject.NetworkShow(lateJoinClientId); // Validate the NetworkObject is visible to all connected clients (including the recently joined client) m_ObserverTestType = ObserverTestTypes.WithObservers; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs index 9c2e0cabaa..f0e01a842c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs @@ -36,7 +36,7 @@ public override void OnNetworkSpawn() ClientTargetedNetworkObjects.Add(this); } - if (IsServer) + if (IsServer || IsSessionOwner) { MyListSetOnSpawn.Add(45); } @@ -46,14 +46,7 @@ public override void OnNetworkSpawn() Debug.Assert(MyListSetOnSpawn[0] == 45); } - if (ObjectsPerClientId.ContainsKey(NetworkManager.LocalClientId)) - { - ObjectsPerClientId[NetworkManager.LocalClientId] = this; - } - else - { - ObjectsPerClientId.Add(NetworkManager.LocalClientId, this); - } + ObjectsPerClientId[NetworkManager.LocalClientId] = this; base.OnNetworkSpawn(); } @@ -132,7 +125,7 @@ internal class NetworkShowHideTests : NetcodeIntegrationTest { protected override int NumberOfClients => 4; - // TODO: [CmbServiceTests] Adapt to run with the service + // TODO: [CmbServiceTests] https://jira.unity3d.com/browse/MTTB-1392 protected override bool UseCMBService() { return false; @@ -166,7 +159,7 @@ private IEnumerator CheckVisible(bool isVisible) int count = 0; do { - yield return WaitForTicks(m_ServerNetworkManager, 5); + yield return WaitForTicks(GetAuthorityNetworkManager(), 5); count++; if (count > 20) @@ -191,7 +184,7 @@ private IEnumerator CheckVisible(bool isVisible) Debug.Assert(m_Object2OnClient0.IsSpawned == isVisible); Debug.Assert(m_Object3OnClient0.IsSpawned == isVisible); - var clientNetworkManager = m_ClientNetworkManagers.Where((c) => c.LocalClientId == m_ClientId0).First(); + var clientNetworkManager = m_ClientNetworkManagers.First(c => c.LocalClientId == m_ClientId0); if (isVisible) { Assert.True(ShowHideObject.ClientTargetedNetworkObjects.Count == 3, $"Client-{clientNetworkManager.LocalClientId} should have 3 instances visible but only has {ShowHideObject.ClientTargetedNetworkObjects.Count}!"); @@ -249,9 +242,10 @@ private bool RefreshNetworkObjects() { return false; } - Assert.True(m_Object1OnClient0.NetworkManagerOwner == m_ClientNetworkManagers[0]); - Assert.True(m_Object2OnClient0.NetworkManagerOwner == m_ClientNetworkManagers[0]); - Assert.True(m_Object3OnClient0.NetworkManagerOwner == m_ClientNetworkManagers[0]); + var nonAuthority = GetNonAuthorityNetworkManager(); + Assert.True(m_Object1OnClient0.NetworkManagerOwner == nonAuthority); + Assert.True(m_Object2OnClient0.NetworkManagerOwner == nonAuthority); + Assert.True(m_Object3OnClient0.NetworkManagerOwner == nonAuthority); return true; } @@ -259,15 +253,16 @@ private bool RefreshNetworkObjects() [UnityTest] public IEnumerator NetworkShowHideTest() { - m_ClientId0 = m_ClientNetworkManagers[0].LocalClientId; + var authority = GetAuthorityNetworkManager(); + m_ClientId0 = GetNonAuthorityNetworkManager().LocalClientId; ShowHideObject.ClientTargetedNetworkObjects.Clear(); ShowHideObject.ClientIdToTarget = m_ClientId0; // create 3 objects - var spawnedObject1 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); - var spawnedObject2 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); - var spawnedObject3 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); + var spawnedObject1 = SpawnObject(m_PrefabToSpawn, authority); + var spawnedObject2 = SpawnObject(m_PrefabToSpawn, authority); + var spawnedObject3 = SpawnObject(m_PrefabToSpawn, authority); m_NetSpawnedObject1 = spawnedObject1.GetComponent(); m_NetSpawnedObject2 = spawnedObject2.GetComponent(); m_NetSpawnedObject3 = spawnedObject3.GetComponent(); @@ -284,11 +279,11 @@ public IEnumerator NetworkShowHideTest() // hide them on one client Show(mode == 0, false); - yield return WaitForTicks(m_ServerNetworkManager, 5); + yield return WaitForTicks(authority, 5); m_NetSpawnedObject1.GetComponent().MyNetworkVariable.Value = 3; - yield return WaitForTicks(m_ServerNetworkManager, 5); + yield return WaitForTicks(authority, 5); // verify they got hidden yield return CheckVisible(false); @@ -306,15 +301,15 @@ public IEnumerator NetworkShowHideTest() [UnityTest] public IEnumerator ConcurrentShowAndHideOnDifferentObjects() { - m_ClientId0 = m_ClientNetworkManagers[0].LocalClientId; + var authority = GetAuthorityNetworkManager(); + m_ClientId0 = GetNonAuthorityNetworkManager().LocalClientId; ShowHideObject.ClientTargetedNetworkObjects.Clear(); ShowHideObject.ClientIdToTarget = m_ClientId0; - // create 3 objects - var spawnedObject1 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); - var spawnedObject2 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); - var spawnedObject3 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); + var spawnedObject1 = SpawnObject(m_PrefabToSpawn, authority); + var spawnedObject2 = SpawnObject(m_PrefabToSpawn, authority); + var spawnedObject3 = SpawnObject(m_PrefabToSpawn, authority); m_NetSpawnedObject1 = spawnedObject1.GetComponent(); m_NetSpawnedObject2 = spawnedObject2.GetComponent(); m_NetSpawnedObject3 = spawnedObject3.GetComponent(); @@ -325,24 +320,25 @@ public IEnumerator ConcurrentShowAndHideOnDifferentObjects() m_NetSpawnedObject1.NetworkHide(m_ClientId0); - yield return WaitForTicks(m_ServerNetworkManager, 5); + yield return WaitForTicks(authority, 5); m_NetSpawnedObject1.NetworkShow(m_ClientId0); m_NetSpawnedObject2.NetworkHide(m_ClientId0); - yield return WaitForTicks(m_ServerNetworkManager, 5); + yield return WaitForTicks(authority, 5); } [UnityTest] public IEnumerator NetworkShowHideQuickTest() { - m_ClientId0 = m_ClientNetworkManagers[0].LocalClientId; + var authority = GetAuthorityNetworkManager(); + m_ClientId0 = GetNonAuthorityNetworkManager().LocalClientId; ShowHideObject.ClientTargetedNetworkObjects.Clear(); ShowHideObject.ClientIdToTarget = m_ClientId0; - var spawnedObject1 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); - var spawnedObject2 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); - var spawnedObject3 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); + var spawnedObject1 = SpawnObject(m_PrefabToSpawn, authority); + var spawnedObject2 = SpawnObject(m_PrefabToSpawn, authority); + var spawnedObject3 = SpawnObject(m_PrefabToSpawn, authority); m_NetSpawnedObject1 = spawnedObject1.GetComponent(); m_NetSpawnedObject2 = spawnedObject2.GetComponent(); m_NetSpawnedObject3 = spawnedObject3.GetComponent(); @@ -360,10 +356,10 @@ public IEnumerator NetworkShowHideQuickTest() Show(mode == 0, false); Show(mode == 0, true); - yield return WaitForTicks(m_ServerNetworkManager, 5); + yield return WaitForTicks(authority, 5); yield return WaitForConditionOrTimeOut(RefreshNetworkObjects); AssertOnTimeout($"Could not refresh all NetworkObjects!"); - yield return WaitForTicks(m_ServerNetworkManager, 5); + yield return WaitForTicks(authority, 5); // verify they become visible yield return CheckVisible(true); @@ -373,14 +369,15 @@ public IEnumerator NetworkShowHideQuickTest() [UnityTest] public IEnumerator NetworkHideDespawnTest() { - m_ClientId0 = m_ClientNetworkManagers[0].LocalClientId; + var authority = GetAuthorityNetworkManager(); + m_ClientId0 = GetNonAuthorityNetworkManager().LocalClientId; ShowHideObject.ClientTargetedNetworkObjects.Clear(); ShowHideObject.ClientIdToTarget = m_ClientId0; ShowHideObject.Silent = true; - var spawnedObject1 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); - var spawnedObject2 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); - var spawnedObject3 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); + var spawnedObject1 = SpawnObject(m_PrefabToSpawn, authority); + var spawnedObject2 = SpawnObject(m_PrefabToSpawn, authority); + var spawnedObject3 = SpawnObject(m_PrefabToSpawn, authority); m_NetSpawnedObject1 = spawnedObject1.GetComponent(); m_NetSpawnedObject2 = spawnedObject2.GetComponent(); m_NetSpawnedObject3 = spawnedObject3.GetComponent(); @@ -389,7 +386,7 @@ public IEnumerator NetworkHideDespawnTest() m_NetSpawnedObject1.NetworkHide(m_ClientId0); m_NetSpawnedObject1.Despawn(); - yield return WaitForTicks(m_ServerNetworkManager, 5); + yield return WaitForTicks(authority, 5); LogAssert.NoUnexpectedReceived(); } @@ -400,19 +397,11 @@ public IEnumerator NetworkHideDespawnTest() private bool CheckListedClientsVisibility() { - if (m_ClientsWithVisibility.Contains(m_ServerNetworkManager.LocalClientId)) + foreach (var manager in m_NetworkManagers) { - if (!m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_ObserverTestObject.NetworkObjectId)) + if (m_ClientsWithVisibility.Contains(manager.LocalClientId)) { - return false; - } - } - - foreach (var client in m_ClientNetworkManagers) - { - if (m_ClientsWithVisibility.Contains(client.LocalClientId)) - { - if (!client.SpawnManager.SpawnedObjects.ContainsKey(m_ObserverTestObject.NetworkObjectId)) + if (!manager.SpawnManager.SpawnedObjects.ContainsKey(m_ObserverTestObject.NetworkObjectId)) { return false; } @@ -424,11 +413,14 @@ private bool CheckListedClientsVisibility() [UnityTest] public IEnumerator SpawnWithoutObserversTest() { - var spawnedObject = SpawnObject(m_PrefabSpawnWithoutObservers, m_ServerNetworkManager); + var authority = GetAuthorityNetworkManager(); + var nonAuthorityNetworkManager = GetNonAuthorityNetworkManager(); + + var spawnedObject = SpawnObject(m_PrefabSpawnWithoutObservers, authority); m_ObserverTestObject = spawnedObject.GetComponent(); - yield return WaitForTicks(m_ServerNetworkManager, 3); + yield return WaitForTicks(authority, 3); // When in client-server, the server can spawn a NetworkObject without any observers (even when running as a host the host-client should not have visibility) // When in distributed authority mode, the owner client has to be an observer of the object @@ -436,9 +428,9 @@ public IEnumerator SpawnWithoutObserversTest() { // No observers should be assigned at this point Assert.True(m_ObserverTestObject.Observers.Count == m_ClientsWithVisibility.Count, $"Expected the observer count to be {m_ClientsWithVisibility.Count} but it was {m_ObserverTestObject.Observers.Count}!"); - m_ObserverTestObject.NetworkShow(m_ServerNetworkManager.LocalClientId); + m_ObserverTestObject.NetworkShow(authority.LocalClientId); } - m_ClientsWithVisibility.Add(m_ServerNetworkManager.LocalClientId); + m_ClientsWithVisibility.Add(authority.LocalClientId); Assert.True(m_ObserverTestObject.Observers.Count == m_ClientsWithVisibility.Count, $"Expected the observer count to be {m_ClientsWithVisibility.Count} but it was {m_ObserverTestObject.Observers.Count}!"); @@ -447,6 +439,10 @@ public IEnumerator SpawnWithoutObserversTest() foreach (var client in m_ClientNetworkManagers) { + if (client.LocalClient.IsSessionOwner) + { + continue; + } m_ObserverTestObject.NetworkShow(client.LocalClientId); m_ClientsWithVisibility.Add(client.LocalClientId); Assert.True(m_ObserverTestObject.Observers.Contains(client.LocalClientId), $"[NetworkShow] Client-{client.LocalClientId} is still not an observer!"); @@ -477,6 +473,11 @@ private bool Object1IsNotVisibileToClient() m_ErrorLog.Clear(); foreach (var client in m_ClientNetworkManagers) { + if (client.LocalClient.IsSessionOwner) + { + continue; + } + if (client.LocalClientId == m_ClientWithoutVisibility) { if (client.SpawnManager.SpawnedObjects.ContainsKey(m_NetSpawnedObject1.NetworkObjectId)) @@ -496,11 +497,15 @@ private bool Object1IsNotVisibileToClient() [UnityTest] public IEnumerator NetworkHideChangeOwnership() { + var authority = GetAuthorityNetworkManager(); + var firstClient = GetNonAuthorityNetworkManager(0); + var secondClient = GetNonAuthorityNetworkManager(1); + ShowHideObject.ClientTargetedNetworkObjects.Clear(); - ShowHideObject.ClientIdToTarget = m_ClientNetworkManagers[1].LocalClientId; + ShowHideObject.ClientIdToTarget = secondClient.LocalClientId; ShowHideObject.Silent = true; - var spawnedObject1 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); + var spawnedObject1 = SpawnObject(m_PrefabToSpawn, authority); m_NetSpawnedObject1 = spawnedObject1.GetComponent(); yield return WaitForConditionOrTimeOut(ClientsSpawnedObject1); @@ -508,8 +513,8 @@ public IEnumerator NetworkHideChangeOwnership() m_NetSpawnedObject1.GetComponent().MyNetworkVariable.Value++; // Hide an object to a client - m_NetSpawnedObject1.NetworkHide(m_ClientNetworkManagers[1].LocalClientId); - m_ClientWithoutVisibility = m_ClientNetworkManagers[1].LocalClientId; + m_NetSpawnedObject1.NetworkHide(secondClient.LocalClientId); + m_ClientWithoutVisibility = secondClient.LocalClientId; yield return WaitForConditionOrTimeOut(Object1IsNotVisibileToClient); AssertOnTimeout($"NetworkObject is still visible to Client-{m_ClientWithoutVisibility} or other clients think it is still visible to Client-{m_ClientWithoutVisibility}:\n {m_ErrorLog}"); @@ -518,16 +523,16 @@ public IEnumerator NetworkHideChangeOwnership() foreach (var client in m_ClientNetworkManagers) { - if (m_ClientNetworkManagers[1].LocalClientId == client.LocalClientId) + if (secondClient.LocalClientId == client.LocalClientId) { continue; } var clientInstance = client.SpawnManager.SpawnedObjects[m_NetSpawnedObject1.NetworkObjectId]; - Assert.IsFalse(clientInstance.IsNetworkVisibleTo(m_ClientNetworkManagers[1].LocalClientId), $"Object instance on Client-{client.LocalClientId} is still visible to Client-{m_ClientNetworkManagers[1].LocalClientId}!"); + Assert.IsFalse(clientInstance.IsNetworkVisibleTo(secondClient.LocalClientId), $"Object instance on Client-{client.LocalClientId} is still visible to Client-{secondClient.LocalClientId}!"); } // Change ownership while the object is hidden to some - m_NetSpawnedObject1.ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); + m_NetSpawnedObject1.ChangeOwnership(firstClient.LocalClientId); // The two-second wait is actually needed as there's a potential warning of unhandled message after 1 second yield return new WaitForSeconds(1.25f); @@ -536,25 +541,30 @@ public IEnumerator NetworkHideChangeOwnership() // Show the object again to check nothing unexpected happens if (m_DistributedAuthority) { - Assert.True(m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.ContainsKey(m_NetSpawnedObject1.NetworkObjectId), $"Client-{m_ClientNetworkManagers[0].LocalClientId} has no spawned object with an ID of: {m_NetSpawnedObject1.NetworkObjectId}!"); - var clientInstance = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[m_NetSpawnedObject1.NetworkObjectId]; - Assert.True(clientInstance.HasAuthority, $"Client-{m_ClientNetworkManagers[0].LocalClientId} does not have authority to hide NetworkObject ID: {m_NetSpawnedObject1.NetworkObjectId}!"); - clientInstance.NetworkShow(m_ClientNetworkManagers[1].LocalClientId); + Assert.True(firstClient.SpawnManager.SpawnedObjects.ContainsKey(m_NetSpawnedObject1.NetworkObjectId), $"Client-{firstClient.LocalClientId} has no spawned object with an ID of: {m_NetSpawnedObject1.NetworkObjectId}!"); + var clientInstance = firstClient.SpawnManager.SpawnedObjects[m_NetSpawnedObject1.NetworkObjectId]; + Assert.True(clientInstance.HasAuthority, $"Client-{firstClient.LocalClientId} does not have authority to hide NetworkObject ID: {m_NetSpawnedObject1.NetworkObjectId}!"); + clientInstance.NetworkShow(secondClient.LocalClientId); } else { - m_NetSpawnedObject1.NetworkShow(m_ClientNetworkManagers[1].LocalClientId); + m_NetSpawnedObject1.NetworkShow(secondClient.LocalClientId); } yield return WaitForConditionOrTimeOut(() => ShowHideObject.ClientTargetedNetworkObjects.Count == 1); - Assert.True(ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId == m_ClientNetworkManagers[0].LocalClientId); + Assert.True(ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId == firstClient.LocalClientId); } private bool AllClientsSpawnedObject1() { foreach (var client in m_ClientNetworkManagers) { + if (client.LocalClient.IsSessionOwner) + { + continue; + } + if (!ShowHideObject.ObjectsPerClientId.ContainsKey(client.LocalClientId)) { return false; @@ -566,12 +576,16 @@ private bool AllClientsSpawnedObject1() [UnityTest] public IEnumerator NetworkHideChangeOwnershipNotHidden() { + var authority = GetAuthorityNetworkManager(); + var firstClient = GetNonAuthorityNetworkManager(0); + var secondClient = GetNonAuthorityNetworkManager(1); + ShowHideObject.ClientTargetedNetworkObjects.Clear(); ShowHideObject.ObjectsPerClientId.Clear(); - ShowHideObject.ClientIdToTarget = m_ClientNetworkManagers[1].LocalClientId; + ShowHideObject.ClientIdToTarget = secondClient.LocalClientId; ShowHideObject.Silent = true; - var spawnedObject1 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); + var spawnedObject1 = SpawnObject(m_PrefabToSpawn, authority); m_NetSpawnedObject1 = spawnedObject1.GetComponent(); yield return WaitForConditionOrTimeOut(AllClientsSpawnedObject1); @@ -581,23 +595,23 @@ public IEnumerator NetworkHideChangeOwnershipNotHidden() m_NetSpawnedObject1.GetComponent().MyOwnerReadNetworkVariable.Value++; // wait for three ticks - yield return WaitForTicks(m_ServerNetworkManager, 3); + yield return WaitForTicks(authority, 3); // check we'll actually be changing owners - Assert.False(ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId == m_ClientNetworkManagers[0].LocalClientId); + Assert.False(ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId == firstClient.LocalClientId); // only check for value change on one specific client - ShowHideObject.NetworkManagerOfInterest = m_ClientNetworkManagers[0]; + ShowHideObject.NetworkManagerOfInterest = firstClient; // change ownership - m_NetSpawnedObject1.ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); + m_NetSpawnedObject1.ChangeOwnership(firstClient.LocalClientId); // wait three ticks - yield return WaitForTicks(m_ServerNetworkManager, 3); - yield return WaitForTicks(m_ClientNetworkManagers[0], 3); + yield return WaitForTicks(authority, 3); + yield return WaitForTicks(firstClient, 3); // verify ownership changed - Assert.AreEqual(ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId, m_ClientNetworkManagers[0].LocalClientId); + Assert.AreEqual(ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId, firstClient.LocalClientId); // verify the expected client got the OnValueChanged. (Only client 1 sets this value) Assert.AreEqual(1, ShowHideObject.ValueAfterOwnershipChange); @@ -637,79 +651,83 @@ private void Compare(NetworkList list1, NetworkList list2) Debug.Assert(list1.Count == list2.Count); } - private IEnumerator HideThenShowAndHideThenModifyAndShow() + private IEnumerator HideThenShowAndHideThenModifyAndShow(NetworkManager authority, NetworkManager nonAuthority) { VerboseDebug("Hiding"); // hide - m_NetSpawnedObject1.NetworkHide(1); - yield return WaitForTicks(m_ServerNetworkManager, 3); - yield return WaitForTicks(m_ClientNetworkManagers[0], 3); + m_NetSpawnedObject1.NetworkHide(nonAuthority.LocalClientId); + yield return WaitForTicks(authority, 3); + yield return WaitForTicks(nonAuthority, 3); VerboseDebug("Showing and Hiding"); // show and hide - m_NetSpawnedObject1.NetworkShow(1); - m_NetSpawnedObject1.NetworkHide(1); - yield return WaitForTicks(m_ServerNetworkManager, 3); - yield return WaitForTicks(m_ClientNetworkManagers[0], 3); + m_NetSpawnedObject1.NetworkShow(nonAuthority.LocalClientId); + m_NetSpawnedObject1.NetworkHide(nonAuthority.LocalClientId); + yield return WaitForTicks(authority, 3); + yield return WaitForTicks(nonAuthority, 3); VerboseDebug("Modifying and Showing"); // modify and show m_NetSpawnedObject1.GetComponent().MyList.Add(5); - m_NetSpawnedObject1.NetworkShow(1); - yield return WaitForTicks(m_ServerNetworkManager, 3); - yield return WaitForTicks(m_ClientNetworkManagers[0], 3); + m_NetSpawnedObject1.NetworkShow(nonAuthority.LocalClientId); + yield return WaitForTicks(authority, 3); + yield return WaitForTicks(nonAuthority, 3); } - private IEnumerator HideThenModifyAndShow() + private IEnumerator HideThenModifyAndShow(NetworkManager authority, NetworkManager nonAuthority) { // hide - m_NetSpawnedObject1.NetworkHide(1); - yield return WaitForTicks(m_ServerNetworkManager, 3); + m_NetSpawnedObject1.NetworkHide(nonAuthority.LocalClientId); + yield return WaitForTicks(authority, 3); // modify m_NetSpawnedObject1.GetComponent().MyList.Add(5); // show - m_NetSpawnedObject1.NetworkShow(1); - yield return WaitForTicks(m_ServerNetworkManager, 3); - yield return WaitForTicks(m_ClientNetworkManagers[0], 3); + m_NetSpawnedObject1.NetworkShow(nonAuthority.LocalClientId); + yield return WaitForTicks(authority, 3); + yield return WaitForTicks(nonAuthority, 3); } - private IEnumerator HideThenShowAndModify() + private IEnumerator HideThenShowAndModify(NetworkManager authority, NetworkManager nonAuthority) { // hide - m_NetSpawnedObject1.NetworkHide(1); - yield return WaitForTicks(m_ServerNetworkManager, 3); + m_NetSpawnedObject1.NetworkHide(nonAuthority.LocalClientId); + yield return WaitForTicks(authority, 3); // show - m_NetSpawnedObject1.NetworkShow(1); + m_NetSpawnedObject1.NetworkShow(nonAuthority.LocalClientId); // modify m_NetSpawnedObject1.GetComponent().MyList.Add(5); - yield return WaitForTicks(m_ServerNetworkManager, 3); - yield return WaitForTicks(m_ClientNetworkManagers[0], 3); + yield return WaitForTicks(authority, 3); + yield return WaitForTicks(nonAuthority, 3); } - private IEnumerator HideThenShowAndRPC() + private IEnumerator HideThenShowAndRPC(NetworkManager authority, ulong nonAuthorityId) { // hide - m_NetSpawnedObject1.NetworkHide(1); - yield return WaitForTicks(m_ServerNetworkManager, 3); + m_NetSpawnedObject1.NetworkHide(nonAuthorityId); + yield return WaitForTicks(authority, 3); // show - m_NetSpawnedObject1.NetworkShow(1); + m_NetSpawnedObject1.NetworkShow(nonAuthorityId); m_NetSpawnedObject1.GetComponent().TriggerRpc(); - yield return WaitForTicks(m_ServerNetworkManager, 3); + yield return WaitForTicks(authority, 3); } [UnityTest] public IEnumerator NetworkShowHideAroundListModify() { + var authority = GetAuthorityNetworkManager(); + var firstClient = GetNonAuthorityNetworkManager(0); + var secondClient = GetNonAuthorityNetworkManager(1); + ShowHideObject.ClientTargetedNetworkObjects.Clear(); - ShowHideObject.ClientIdToTarget = m_ClientNetworkManagers[1].LocalClientId; + ShowHideObject.ClientIdToTarget = secondClient.LocalClientId; ShowHideObject.Silent = true; - var spawnedObject1 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager); + var spawnedObject1 = SpawnObject(m_PrefabToSpawn, authority); m_NetSpawnedObject1 = spawnedObject1.GetComponent(); // wait for host to have spawned and gained ownership @@ -721,27 +739,27 @@ public IEnumerator NetworkShowHideAroundListModify() for (int i = 0; i < 4; i++) { // wait for three ticks - yield return WaitForTicks(m_ServerNetworkManager, 3); - yield return WaitForTicks(m_ClientNetworkManagers[0], 3); + yield return WaitForTicks(authority, 3); + yield return WaitForTicks(firstClient, 3); switch (i) { case 0: VerboseDebug("Running HideThenModifyAndShow"); - yield return HideThenModifyAndShow(); + yield return HideThenModifyAndShow(authority, firstClient); break; case 1: VerboseDebug("Running HideThenShowAndModify"); - yield return HideThenShowAndModify(); + yield return HideThenShowAndModify(authority, firstClient); break; case 2: VerboseDebug("Running HideThenShowAndHideThenModifyAndShow"); - yield return HideThenShowAndHideThenModifyAndShow(); + yield return HideThenShowAndHideThenModifyAndShow(authority, firstClient); break; case 3: VerboseDebug("Running HideThenShowAndRPC"); ShowHideObject.ClientIdsRpcCalledOn = new List(); - yield return HideThenShowAndRPC(); + yield return HideThenShowAndRPC(authority, firstClient.LocalClientId); // Provide enough time for slower systems or VM systems possibly under a heavy load could fail on this test yield return WaitForConditionOrTimeOut(() => ShowHideObject.ClientIdsRpcCalledOn.Count == NumberOfClients + 1); AssertOnTimeout($"Timed out waiting for ClientIdsRpcCalledOn.Count ({ShowHideObject.ClientIdsRpcCalledOn.Count}) to equal ({NumberOfClients + 1})!"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index ffe08412f8..bc647dffde 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -283,7 +283,38 @@ protected NetworkManager GetAuthorityNetworkManager() /// A instance that will not be the session owner protected NetworkManager GetNonAuthorityNetworkManager() { - return m_ClientNetworkManagers.First(client => !client.LocalClient.IsSessionOwner); + // Return the equivalent of m_ClientNetworkManagers[0] + return GetNonAuthorityNetworkManager(0); + } + + /// + /// Gets the non-session owner indicated by the passed in index. + /// + /// Index of the number of the non-authority client wanted + /// The instance that is not the session owner at the given index + protected NetworkManager GetNonAuthorityNetworkManager(int nonAuthorityIndex) + { + var numSeen = 0; + for (int i = 0; i < m_ClientNetworkManagers.Length; i++) + { + if (i == 0 && !NetcodeIntegrationTestHelpers.IsStarted) + { + continue; + } + + if (!m_ClientNetworkManagers[i].LocalClient.IsSessionOwner) + { + if (numSeen == nonAuthorityIndex) + { + return m_ClientNetworkManagers[i]; + } + + numSeen++; + } + + } + Assert.Fail("No valid non-authority network manager found!"); + return null; } /// @@ -636,7 +667,7 @@ public IEnumerator SetUp() ComponentFactory.Register(manager => new MockTimeProvider()); } - if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.AllTests && m_ServerNetworkManager == null || + if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.AllTests && !NetcodeIntegrationTestHelpers.IsStarted || m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.PerTest) { CreateServerAndClients(); @@ -789,7 +820,9 @@ protected void CreateServerAndClients(int numberOfClients) protected virtual void OnNewClientCreated(NetworkManager networkManager) { // Ensure any late joining client has all NetworkPrefabs required to connect. - foreach (var networkPrefab in GetAuthorityNetworkManager().NetworkConfig.Prefabs.Prefabs) + var authority = GetAuthorityNetworkManager(); + networkManager.NetworkConfig.EnableSceneManagement = authority.NetworkConfig.EnableSceneManagement; + foreach (var networkPrefab in authority.NetworkConfig.Prefabs.Prefabs) { if (!networkManager.NetworkConfig.Prefabs.Contains(networkPrefab.Prefab)) { @@ -832,11 +865,11 @@ protected virtual bool ShouldWaitForNewClientToConnect(NetworkManager networkMan } /// - /// This will create, start, and connect a new client while in the middle of an - /// integration test. + /// This will create a new client while in the middle of an integration test. + /// Use to start the created client. /// - /// An to be used in a coroutine for asynchronous execution. - protected IEnumerator CreateAndStartNewClient() + /// The newly created . + protected NetworkManager CreateNewClient() { var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length, m_EnableTimeTravel, m_UseCmbService); networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; @@ -846,6 +879,17 @@ protected IEnumerator CreateAndStartNewClient() // in the event any modifications need to be made before starting the client OnNewClientCreated(networkManager); + return networkManager; + } + + /// + /// This will create, start, and connect a new client while in the middle of an + /// integration test. + /// + /// An to be used in a coroutine for asynchronous execution. + protected IEnumerator CreateAndStartNewClient() + { + var networkManager = CreateNewClient(); yield return StartClient(networkManager); } @@ -1882,14 +1926,13 @@ protected IEnumerator WaitForClientsConnectedOrTimeOut(NetworkManager[] clientsT private bool CheckClientsConnected(NetworkManager[] clientsToCheck) { m_InternalErrorLog.Clear(); - var allClientsConnected = true; for (int i = 0; i < clientsToCheck.Length; i++) { if (!clientsToCheck[i].IsConnectedClient) { - allClientsConnected = false; m_InternalErrorLog.AppendLine($"[Client-{i + 1}] Client is not connected!"); + return false; } } @@ -1898,11 +1941,11 @@ private bool CheckClientsConnected(NetworkManager[] clientsToCheck) if (currentCount != TotalClients) { - allClientsConnected = false; m_InternalErrorLog.AppendLine($"[Server-Side] Expected {TotalClients} clients to connect but only {currentCount} connected!"); + return false; } - return allClientsConnected; + return true; } /// diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs index e11d1723f4..61d6f53b1f 100644 --- a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/InSceneParentChildHandler.cs @@ -371,7 +371,7 @@ private void SendTransformInfoRpc(ChildrenInfo childrenInfo) { m_RequestSent = false; var errorCount = 0; - var autoSync = ParentingAutoSyncManager.ServerInstance; + var autoSync = ParentingAutoSyncManager.AuthorityInstance; var position = Vector3.zero; var rotation = Vector3.zero; var scale = Vector3.zero; diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentingAutoSyncManager.cs b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentingAutoSyncManager.cs index 777c379c44..32b05c20fb 100644 --- a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentingAutoSyncManager.cs +++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ParentingAutoSyncManager.cs @@ -11,7 +11,7 @@ namespace TestProject.ManualTests /// public class ParentingAutoSyncManager : NetworkBehaviour { - public static ParentingAutoSyncManager ServerInstance; + public static ParentingAutoSyncManager AuthorityInstance; public static Dictionary ClientInstances = new Dictionary(); public GameObject WithNetworkObjectAutoSyncOn; @@ -26,15 +26,15 @@ public class ParentingAutoSyncManager : NetworkBehaviour public static void Reset() { - ServerInstance = null; + AuthorityInstance = null; ClientInstances.Clear(); } public override void OnNetworkSpawn() { - if (IsServer) + if (IsServer || IsSessionOwner) { - ServerInstance = this; + AuthorityInstance = this; } else { diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs new file mode 100644 index 0000000000..c5da158cc9 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs @@ -0,0 +1,218 @@ +using System.Collections; +using System.Linq; +using NUnit.Framework; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; + +namespace TestProject.RuntimeTests +{ + [TestFixture(NetworkTopologyTypes.DistributedAuthority, DespawnMode.Despawn)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority, DespawnMode.DeferDespawn)] + [TestFixture(NetworkTopologyTypes.ClientServer, DespawnMode.Despawn)] + public class InScenePlacedNetworkObjectDestroyTests : IntegrationTestWithApproximation + { + protected override int NumberOfClients => 2; + + private const string k_SceneToLoad = "InSceneNetworkObject"; + private Scene m_ServerSideSceneLoaded; + + // private string m_SceneLoading = k_SceneToLoad; + private readonly DespawnMode m_DespawnMode; + + public InScenePlacedNetworkObjectDestroyTests(NetworkTopologyTypes networkTopologyType, DespawnMode despawnMode) : base(networkTopologyType) + { + m_DespawnMode = despawnMode; + } + + protected override IEnumerator OnSetup() + { + NetworkObjectTestComponent.VerboseDebug = m_EnableVerboseDebug; + return base.OnSetup(); + } + + /// + /// Very important to always have a backup "unloading" catch + /// in the event your test fails it could not potentially unload + /// a scene and the proceeding tests could be impacted by this! + /// + /// + protected override IEnumerator OnTearDown() + { + NetworkObjectTestComponent.Reset(); + yield return CleanUpLoadedScene(); + } + + public enum DespawnMode + { + Despawn, + DeferDespawn, + } + + private enum DestroyMode + { + DestroyGameObject, + DontDestroyGameObject, + } + + /// + /// This verifies that in-scene placed NetworkObjects are properly handled when they are called with NetworkObject.Despawn(false) + /// + [UnityTest] + public IEnumerator InSceneNetworkObjectDestroy() + { + yield return LoadSceneAndDespawnObject(DestroyMode.DestroyGameObject); + + // Late joining a client when destroying a game object is not a supported pattern. + } + + /// + /// This verifies NetworkObject.Despawn() works as expected with the given option for destroyGameObject + /// Used by other tests to test specific use cases. + /// + private IEnumerator LoadSceneAndDespawnObject(DestroyMode destroyMode) + { + var authority = GetAuthorityNetworkManager(); + var destroyGameObject = destroyMode == DestroyMode.DestroyGameObject; + + authority.SceneManager.OnSceneEvent += Server_OnSceneEvent; + VerboseDebug("Loading scene"); + var status = authority.SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Additive); + Assert.IsTrue(status == SceneEventProgressStatus.Started, $"When attempting to load scene {k_SceneToLoad} was returned the following progress status: {status}"); + + // This verifies the scene loaded and the in-scene placed NetworkObjects spawned. + yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients); + AssertOnTimeout($"Timed out waiting for total spawned in-scene placed NetworkObjects to reach a count of {TotalClients} and is currently {NetworkObjectTestComponent.SpawnedInstances.Count}"); + + yield return WaitForConditionOrTimeOut(() => m_ServerSideSceneLoaded.IsValid() && m_ServerSideSceneLoaded.isLoaded); + AssertOnTimeout($"Timed out waiting for server to finish loading scene {k_SceneToLoad}!"); + + // Get the server-side instance of the in-scene NetworkObject + Assert.True(s_GlobalNetworkObjects.ContainsKey(authority.LocalClientId), "Could not find server instance of the test in-scene NetworkObject!"); + var serverObject = NetworkObjectTestComponent.ServerNetworkObjectInstance; + var serverObjectId = serverObject.NetworkObjectId; + var spawnedObjects = NetworkObjectTestComponent.SpawnedObjects; + Assert.IsNotNull(serverObject, "Could not find server-side in-scene placed NetworkObject!"); + Assert.IsTrue(serverObject.IsSpawned, $"{serverObject.name} is not spawned!"); + + VerboseDebug("Doing despawn"); + // Despawn the in-scene placed NetworkObject + if (m_DespawnMode == DespawnMode.Despawn) + { + serverObject.Despawn(destroyGameObject); + } + else + { + serverObject.DeferDespawn(1, destroyGameObject); + } + + yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == 0); + AssertOnTimeout($"Timed out waiting for all in-scene instances to be despawned! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()}"); + + + foreach (var manager in m_NetworkManagers) + { + Assert.False(manager.SpawnManager.SpawnedObjects.ContainsKey(serverObjectId)); + } + + foreach (var spawnedObject in spawnedObjects) + { + if (destroyMode == DestroyMode.DestroyGameObject) + { + Assert.True(spawnedObject == null, "Expected game object to be destroyed!"); + } + else + { + Assert.False(spawnedObject == null, "Expected game object to still exist!"); + } + } + } + + /// + /// This verifies that in-scene placed NetworkObjects will be properly + /// synchronized if: + /// 1.) Despawned prior to a client late-joining + /// 2.) Re-spawned after having been despawned without registering the in-scene + /// NetworkObject as a NetworkPrefab + /// + [UnityTest] + public IEnumerator InSceneNetworkObjectDespawnSyncAndSpawn() + { + yield return LoadSceneAndDespawnObject(DestroyMode.DontDestroyGameObject); + + var serverObject = NetworkObjectTestComponent.ServerNetworkObjectInstance; + + Assert.IsNotNull(serverObject, "Could not find server-side in-scene placed NetworkObject!"); + + VerboseDebug("Late joining client"); + // Now late join a client + NetworkObjectTestComponent.OnInSceneObjectDespawned += OnInSceneObjectDespawned; + + var lateJoinClient = CreateNewClient(); + yield return StartClient(lateJoinClient); + + // Make sure the late-joining client's in-scene placed NetworkObject received the despawn notification during synchronization + Assert.IsNotNull(m_JoinedClientDespawnedNetworkObject, $"{lateJoinClient.name} did not despawn the in-scene placed NetworkObject when connecting and synchronizing!"); + + // We should still have no spawned in-scene placed NetworkObjects at this point + yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == 0); + AssertOnTimeout($"{lateJoinClient.name} spawned in-scene placed NetworkObject!"); + + VerboseDebug("Respawn despawned object"); + // Now test that the despawned in-scene placed NetworkObject can be re-spawned (without having been registered as a NetworkPrefab) + serverObject.Spawn(); + + yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients); + AssertOnTimeout($"Timed out waiting for all in-scene instances to be spawned! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {TotalClients}"); + + VerboseDebug("Network hiding object on first client"); + + // Test NetworkHide on the first client + var firstClientId = GetNonAuthorityNetworkManager(0).LocalClientId; + + serverObject.NetworkHide(firstClientId); + var visibleCount = TotalClients - 1; + + yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == visibleCount); + AssertOnTimeout($"[NetworkHide] Timed out waiting for Client-{firstClientId} to despawn the in-scene placed NetworkObject! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {visibleCount}"); + + VerboseDebug("Network showing object on first client"); + // Validate that the first client can spawn the "netcode hidden" in-scene placed NetworkObject + serverObject.NetworkShow(firstClientId); + + yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients); + AssertOnTimeout($"[NetworkShow] Timed out waiting for Client-{firstClientId} to spawn the in-scene placed NetworkObject! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {TotalClients}"); + + yield return CleanUpLoadedScene(); + } + + private NetworkObject m_JoinedClientDespawnedNetworkObject; + + private void OnInSceneObjectDespawned(NetworkObject networkObject) + { + m_JoinedClientDespawnedNetworkObject = networkObject; + NetworkObjectTestComponent.OnInSceneObjectDespawned -= OnInSceneObjectDespawned; + } + + private void Server_OnSceneEvent(SceneEvent sceneEvent) + { + if (sceneEvent.ClientId == GetAuthorityNetworkManager().LocalClientId && sceneEvent.SceneEventType == SceneEventType.LoadComplete + && sceneEvent.Scene.IsValid() && sceneEvent.Scene.isLoaded) + { + m_ServerSideSceneLoaded = sceneEvent.Scene; + GetAuthorityNetworkManager().SceneManager.OnSceneEvent -= Server_OnSceneEvent; + } + } + + private IEnumerator CleanUpLoadedScene() + { + if (m_ServerSideSceneLoaded.IsValid() && m_ServerSideSceneLoaded.isLoaded) + { + GetAuthorityNetworkManager().SceneManager.UnloadScene(m_ServerSideSceneLoaded); + yield return WaitForConditionOrTimeOut(() => m_ClientNetworkManagers.Any(c => c.IsListening)); + AssertOnTimeout($"[CleanUpLoadedScene] Timed out waiting for all in-scene instances to be despawned! Current spawned count: {m_ClientNetworkManagers.Count(c => !c.IsListening)}"); + } + } + } +} diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs.meta new file mode 100644 index 0000000000..e07ec78d5c --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0e794418a54341c391d527b5572b39b6 +timeCreated: 1750262429 \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs index 54cb4c7be4..55bb3d75b8 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs @@ -54,106 +54,6 @@ protected override void OnNewClientCreated(NetworkManager networkManager) base.OnNewClientCreated(networkManager); } - public enum DespawnMode - { - Despawn, - DeferDespawn, - } - - /// - /// This verifies that in-scene placed NetworkObjects will be properly - /// synchronized if: - /// 1.) Despawned prior to a client late-joining - /// 2.) Re-spawned after having been despawned without registering the in-scene - /// NetworkObject as a NetworkPrefab - /// - [UnityTest] - public IEnumerator InSceneNetworkObjectSynchAndSpawn([Values] DespawnMode despawnMode) - { - if (!m_DistributedAuthority && despawnMode == DespawnMode.DeferDespawn) - { - Assert.Ignore("Test ignored as DeferDespawn is only valid with Distributed Authority mode."); - } - - var authority = GetAuthorityNetworkManager(); - authority.SceneManager.OnSceneEvent += Server_OnSceneEvent; - var status = authority.SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Additive); - Assert.IsTrue(status == SceneEventProgressStatus.Started, $"When attempting to load scene {k_SceneToLoad} was returned the following progress status: {status}"); - - var clientCount = NumberOfClients + 1; - - // This verifies the scene loaded and the in-scene placed NetworkObjects spawned. - yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == clientCount); - AssertOnTimeout($"Timed out waiting for total spawned in-scene placed NetworkObjects to reach a count of {clientCount} and is currently {NetworkObjectTestComponent.SpawnedInstances.Count}"); - - yield return WaitForConditionOrTimeOut(() => m_ServerSideSceneLoaded.IsValid() && m_ServerSideSceneLoaded.isLoaded); - AssertOnTimeout($"Timed out waiting for server to finish loading scene {k_SceneToLoad}!"); - - // Get the server-side instance of the in-scene NetworkObject - Assert.True(s_GlobalNetworkObjects.ContainsKey(authority.LocalClientId), "Could not find server instance of the test in-scene NetworkObject!"); - var serverObject = NetworkObjectTestComponent.ServerNetworkObjectInstance; - Assert.IsNotNull(serverObject, "Could not find server-side in-scene placed NetworkObject!"); - Assert.IsTrue(serverObject.IsSpawned, $"{serverObject.name} is not spawned!"); - - // Despawn the in-scene placed NetworkObject - if (despawnMode == DespawnMode.Despawn) - { - serverObject.Despawn(false); - } - else - { - serverObject.DeferDespawn(1, false); - } - - yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == 0); - AssertOnTimeout($"Timed out waiting for all in-scene instances to be despawned! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()}"); - - // Now late join a client - NetworkObjectTestComponent.OnInSceneObjectDespawned += OnInSceneObjectDespawned; - yield return CreateAndStartNewClient(); - // Spawned another client - clientCount++; - - yield return WaitForConditionOrTimeOut(() => (m_LateJoinClient.IsConnectedClient && m_LateJoinClient.IsListening)); - AssertOnTimeout($"Timed out waiting for {m_LateJoinClient.name} to connect!"); - - yield return s_DefaultWaitForTick; - - // Make sure the late-joining client's in-scene placed NetworkObject received the despawn notification during synchronization - Assert.IsNotNull(m_JoinedClientDespawnedNetworkObject, $"{m_LateJoinClient.name} did not despawn the in-scene placed NetworkObject when connecting and synchronizing!"); - - // Update the newly joined client information - ClientNetworkManagerPostStartInit(); - - // We should still have no spawned in-scene placed NetworkObjects at this point - yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == 0); - AssertOnTimeout($"{m_LateJoinClient.name} spawned in-scene placed NetworkObject!"); - - // Now test that the despawned in-scene placed NetworkObject can be re-spawned (without having been registered as a NetworkPrefab) - serverObject.Spawn(); - - yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == clientCount); - AssertOnTimeout($"Timed out waiting for all in-scene instances to be spawned! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {clientCount}"); - - // Test NetworkHide on the first client - var firstClientId = GetNonAuthorityNetworkManager().LocalClientId; - - serverObject.NetworkHide(firstClientId); - clientCount--; - - yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == clientCount); - AssertOnTimeout($"[NetworkHide] Timed out waiting for Client-{firstClientId} to despawn the in-scene placed NetworkObject! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {clientCount}"); - - // Validate that the first client can spawn the "netcode hidden" in-scene placed NetworkObject - serverObject.NetworkShow(firstClientId); - clientCount++; - - yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == clientCount); - AssertOnTimeout($"[NetworkShow] Timed out waiting for Client-{firstClientId} to spawn the in-scene placed NetworkObject! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {clientCount}"); - - yield return CleanUpLoadedScene(); - } - private Scene m_ClientLoadedScene; [UnityTest] @@ -268,7 +168,6 @@ private void Unload_OnSceneEvent(SceneEvent sceneEvent) } } - private bool m_AllClientsLoadedScene; private bool m_AllClientsUnloadedScene; @@ -286,7 +185,7 @@ private bool HaveAllClientsDespawnedInSceneObject() foreach (var despawnedInstance in NetworkObjectTestComponent.DespawnedInstances) { - if (despawnedInstance.gameObject.activeInHierarchy) + if (despawnedInstance && despawnedInstance.gameObject && despawnedInstance.gameObject.activeInHierarchy) { return false; } @@ -365,7 +264,6 @@ public IEnumerator EnableDisableInSceneObjectTests() serverInSceneObjectInstance.gameObject.SetActive(true); serverInSceneObjectInstance.Spawn(); yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedInSceneObject); - AssertOnTimeout($"[Test #2] Timed out waiting for all instances to be enabled and spawned!"); // Test #4: Now unload the in-scene object's scene and scene switch to the same scene while // also having the server-side disable the in-scene placed NetworkObject and verify all @@ -583,6 +481,8 @@ protected bool ScaleValuesMatch(Transform transformA, Transform transformB) } + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] internal class InScenePlacedNetworkObjectClientTests : NetcodeIntegrationTest { private const string k_SceneToLoad = "InSceneNetworkObject"; @@ -591,11 +491,8 @@ internal class InScenePlacedNetworkObjectClientTests : NetcodeIntegrationTest private Scene m_Scene; - - // TODO: [CmbServiceTests] Adapt to run with the service - protected override bool UseCMBService() + public InScenePlacedNetworkObjectClientTests(NetworkTopologyTypes topology) : base(topology) { - return false; } protected override IEnumerator OnSetup() @@ -632,8 +529,8 @@ public IEnumerator DespawnAndDestroyNetworkObjects() yield return s_DefaultWaitForTick; - var insceneObject = GameObject.Find("InSceneObject"); - Assert.IsNotNull(insceneObject, $"Could not find the in-scene placed {nameof(NetworkObject)}: InSceneObject!"); + var inSceneObject = GameObject.Find("InSceneObject"); + Assert.IsNotNull(inSceneObject, $"Could not find the in-scene placed {nameof(NetworkObject)}: InSceneObject!"); } } } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs index 6a8594b2d7..da7aecc63a 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectTestComponent.cs @@ -18,6 +18,7 @@ public class NetworkObjectTestComponent : NetworkBehaviour public static NetworkObject ServerNetworkObjectInstance; public static List SpawnedInstances = new List(); public static List DespawnedInstances = new List(); + public static List SpawnedObjects = new List(); public static void Reset() { @@ -26,6 +27,7 @@ public static void Reset() ServerNetworkObjectInstance = null; SpawnedInstances.Clear(); DespawnedInstances.Clear(); + SpawnedObjects.Clear(); } private Action m_ActionClientConnected; @@ -48,6 +50,8 @@ private void NetworkManager_OnClientConnectedCallback(ulong obj) public override void OnNetworkSpawn() { SpawnedInstances.Add(this); + SpawnedObjects.Add(NetworkObject); + if (DisableOnDespawn) { if (DespawnedInstances.Contains(this)) diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs index b0321a949d..b227eb2fb8 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentingInSceneObjectsTests.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Generic; using System.Text; using NUnit.Framework; using TestProject.ManualTests; @@ -24,12 +25,6 @@ public class ParentingInSceneObjectsTests : IntegrationTestWithApproximation protected override int NumberOfClients => 2; - // TODO: [CmbServiceTests] Adapt to run with the service - protected override bool UseCMBService() - { - return false; - } - public ParentingInSceneObjectsTests(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } protected override void OnOneTimeSetup() @@ -247,9 +242,10 @@ public IEnumerator InSceneParentingTest([Values] ParentingSpace parentingSpace) SceneManager.sceneLoaded += SceneManager_sceneLoaded; SceneManager.LoadScene(k_BaseSceneToLoad, LoadSceneMode.Additive); m_InitialClientsLoadedScene = false; - m_ServerNetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + var authority = GetAuthorityNetworkManager(); + authority.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; - var sceneEventStartedStatus = m_ServerNetworkManager.SceneManager.LoadScene(k_TestSceneToLoad, LoadSceneMode.Additive); + var sceneEventStartedStatus = authority.SceneManager.LoadScene(k_TestSceneToLoad, LoadSceneMode.Additive); Assert.True(sceneEventStartedStatus == SceneEventProgressStatus.Started, $"Failed to load scene {k_TestSceneToLoad} with a return status of {sceneEventStartedStatus}."); yield return WaitForConditionOrTimeOut(() => m_InitialClientsLoadedScene); AssertOnTimeout($"Timed out waiting for all clients to load scene {k_TestSceneToLoad}!"); @@ -425,9 +421,10 @@ public IEnumerator DespawnParentTest([Values] ParentingSpace parentingSpace) SceneManager.sceneLoaded += SceneManager_sceneLoaded; SceneManager.LoadScene(k_BaseSceneToLoad, LoadSceneMode.Additive); m_InitialClientsLoadedScene = false; - m_ServerNetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; - m_ServerNetworkManager.SceneManager.ClientSynchronizationMode = LoadSceneMode.Additive; - var sceneEventStartedStatus = m_ServerNetworkManager.SceneManager.LoadScene(k_TestSceneToLoad, LoadSceneMode.Additive); + var authority = GetAuthorityNetworkManager(); + authority.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + authority.SceneManager.ClientSynchronizationMode = LoadSceneMode.Additive; + var sceneEventStartedStatus = authority.SceneManager.LoadScene(k_TestSceneToLoad, LoadSceneMode.Additive); Assert.True(sceneEventStartedStatus == SceneEventProgressStatus.Started, $"Failed to load scene {k_TestSceneToLoad} with a return status of {sceneEventStartedStatus}."); yield return WaitForConditionOrTimeOut(() => m_InitialClientsLoadedScene); AssertOnTimeout($"Timed out waiting for all clients to load scene {k_TestSceneToLoad}!"); @@ -453,7 +450,7 @@ private void SceneManager_OnSceneEvent(SceneEvent sceneEvent) return; } - if (sceneEvent.ClientId == m_ServerNetworkManager.LocalClientId && sceneEvent.SceneEventType == SceneEventType.LoadEventCompleted) + if (sceneEvent.ClientId == GetAuthorityNetworkManager().LocalClientId && sceneEvent.SceneEventType == SceneEventType.LoadEventCompleted) { m_InitialClientsLoadedScene = true; } @@ -469,9 +466,10 @@ public IEnumerator InSceneNestedUnderGameObjectTest() SceneManager.sceneLoaded += SceneManager_sceneLoaded; SceneManager.LoadScene(k_BaseSceneToLoad, LoadSceneMode.Additive); m_InitialClientsLoadedScene = false; - m_ServerNetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + var authority = GetAuthorityNetworkManager(); + authority.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; - var sceneEventStartedStatus = m_ServerNetworkManager.SceneManager.LoadScene(k_TestSceneToLoad, LoadSceneMode.Additive); + var sceneEventStartedStatus = authority.SceneManager.LoadScene(k_TestSceneToLoad, LoadSceneMode.Additive); Assert.True(sceneEventStartedStatus == SceneEventProgressStatus.Started, $"Failed to load scene {k_TestSceneToLoad} with a return status of {sceneEventStartedStatus}."); yield return WaitForConditionOrTimeOut(() => m_InitialClientsLoadedScene); AssertOnTimeout($"Timed out waiting for all clients to load scene {k_TestSceneToLoad}!"); @@ -489,114 +487,58 @@ public IEnumerator InSceneNestedUnderGameObjectTest() private bool AllClientInstancesMatchServerInstance() { m_ErrorValidationLog.Clear(); - if (ParentingAutoSyncManager.ServerInstance == null) + if (ParentingAutoSyncManager.AuthorityInstance == null) { m_ErrorValidationLog.AppendLine("ServerInstance is null"); return false; } - for (int i = 0; i < ParentingAutoSyncManager.ServerInstance.NetworkObjectAutoSyncOnTransforms.Count; i++) - { - var serverTransformToTest = ParentingAutoSyncManager.ServerInstance.NetworkObjectAutoSyncOnTransforms[i]; - for (int j = 0; j < m_ClientNetworkManagers.Length; j++) - { - var clientRelativeAutoSyncManager = ParentingAutoSyncManager.ClientInstances[m_ClientNetworkManagers[j].LocalClientId]; - var clientTransformToTest = clientRelativeAutoSyncManager.NetworkObjectAutoSyncOnTransforms[i]; - if (!Approximately(clientTransformToTest.position, serverTransformToTest.position)) - { - m_ErrorValidationLog.AppendLine($"[Client-{clientRelativeAutoSyncManager.NetworkManager.LocalClientId}][AutoSync On] {nameof(NetworkObject)}-{clientRelativeAutoSyncManager.NetworkObjectId}'s position {clientRelativeAutoSyncManager.transform.position} does not equal the server-side position {serverTransformToTest.transform.position}"); - return false; - } - - if (!Approximately(clientTransformToTest.rotation, serverTransformToTest.rotation)) - { - m_ErrorValidationLog.AppendLine($"[Client-{clientRelativeAutoSyncManager.NetworkManager.LocalClientId}][AutoSync On] {nameof(NetworkObject)}-{clientRelativeAutoSyncManager.NetworkObjectId}'s rotation {clientRelativeAutoSyncManager.transform.eulerAngles} does not equal the server-side position {serverTransformToTest.transform.eulerAngles}"); - return false; - } - if (!Approximately(clientTransformToTest.localScale, serverTransformToTest.localScale)) - { - m_ErrorValidationLog.AppendLine($"[Client-{clientRelativeAutoSyncManager.NetworkManager.LocalClientId}][AutoSync On] {nameof(NetworkObject)}-{clientRelativeAutoSyncManager.NetworkObjectId}'s scale {clientRelativeAutoSyncManager.transform.localScale} does not equal the server-side position {serverTransformToTest.transform.localScale}"); - return false; - } - } + if (!AllClientInstancesMatchServerInstances("AutoSync On", ParentingAutoSyncManager.AuthorityInstance.NetworkObjectAutoSyncOnTransforms)) + { + return false; } - for (int i = 0; i < ParentingAutoSyncManager.ServerInstance.NetworkObjectAutoSyncOffTransforms.Count; i++) + if (!AllClientInstancesMatchServerInstances("AutoSync Off", ParentingAutoSyncManager.AuthorityInstance.NetworkObjectAutoSyncOffTransforms)) { - var serverTransformToTest = ParentingAutoSyncManager.ServerInstance.NetworkObjectAutoSyncOffTransforms[i]; - for (int j = 0; j < m_ClientNetworkManagers.Length; j++) - { - var clientRelativeAutoSyncManager = ParentingAutoSyncManager.ClientInstances[m_ClientNetworkManagers[j].LocalClientId]; - var clientTransformToTest = clientRelativeAutoSyncManager.NetworkObjectAutoSyncOffTransforms[i]; - if (!Approximately(clientTransformToTest.position, serverTransformToTest.position)) - { - m_ErrorValidationLog.AppendLine($"[Client-{clientRelativeAutoSyncManager.NetworkManager.LocalClientId}][AutoSync Off] {nameof(NetworkObject)}-{clientRelativeAutoSyncManager.NetworkObjectId}'s position {clientRelativeAutoSyncManager.transform.position} does not equal the server-side position {serverTransformToTest.transform.position}"); - return false; - } - - if (!Approximately(clientTransformToTest.rotation, serverTransformToTest.rotation)) - { - m_ErrorValidationLog.AppendLine($"[Client-{clientRelativeAutoSyncManager.NetworkManager.LocalClientId}][AutoSync Off] {nameof(NetworkObject)}-{clientRelativeAutoSyncManager.NetworkObjectId}'s rotation {clientRelativeAutoSyncManager.transform.eulerAngles} does not equal the server-side position {serverTransformToTest.transform.eulerAngles}"); - return false; - } - - if (!Approximately(clientTransformToTest.localScale, serverTransformToTest.localScale)) - { - m_ErrorValidationLog.AppendLine($"[Client-{clientRelativeAutoSyncManager.NetworkManager.LocalClientId}][AutoSync Off] {nameof(NetworkObject)}-{clientRelativeAutoSyncManager.NetworkObjectId}'s scale {clientRelativeAutoSyncManager.transform.localScale} does not equal the server-side position {serverTransformToTest.transform.localScale}"); - return false; - } - } + return false; } - for (int i = 0; i < ParentingAutoSyncManager.ServerInstance.GameObjectAutoSyncOnTransforms.Count; i++) + if (!AllClientInstancesMatchServerInstances("GO-AutoSync On", ParentingAutoSyncManager.AuthorityInstance.GameObjectAutoSyncOnTransforms)) { - var serverTransformToTest = ParentingAutoSyncManager.ServerInstance.GameObjectAutoSyncOnTransforms[i]; - for (int j = 0; j < m_ClientNetworkManagers.Length; j++) - { - var clientRelativeAutoSyncManager = ParentingAutoSyncManager.ClientInstances[m_ClientNetworkManagers[j].LocalClientId]; - var clientTransformToTest = clientRelativeAutoSyncManager.GameObjectAutoSyncOnTransforms[i]; - if (!Approximately(clientTransformToTest.position, serverTransformToTest.position)) - { - m_ErrorValidationLog.AppendLine($"[Client-{clientRelativeAutoSyncManager.NetworkManager.LocalClientId}][GO-AutoSync On] {nameof(NetworkObject)}-{clientRelativeAutoSyncManager.NetworkObjectId}'s position {clientRelativeAutoSyncManager.transform.position} does not equal the server-side position {serverTransformToTest.transform.position}"); - return false; - } - - if (!Approximately(clientTransformToTest.rotation, serverTransformToTest.rotation)) - { - m_ErrorValidationLog.AppendLine($"[Client-{clientRelativeAutoSyncManager.NetworkManager.LocalClientId}][GO-AutoSync On] {nameof(NetworkObject)}-{clientRelativeAutoSyncManager.NetworkObjectId}'s rotation {clientRelativeAutoSyncManager.transform.eulerAngles} does not equal the server-side position {serverTransformToTest.transform.eulerAngles}"); - return false; - } - - if (!Approximately(clientTransformToTest.localScale, serverTransformToTest.localScale)) - { - m_ErrorValidationLog.AppendLine($"[Client-{clientRelativeAutoSyncManager.NetworkManager.LocalClientId}][GO-AutoSync On] {nameof(NetworkObject)}-{clientRelativeAutoSyncManager.NetworkObjectId}'s scale {clientRelativeAutoSyncManager.transform.localScale} does not equal the server-side position {serverTransformToTest.transform.localScale}"); - return false; - } - } + return false; } - for (int i = 0; i < ParentingAutoSyncManager.ServerInstance.GameObjectAutoSyncOffTransforms.Count; i++) + return AllClientInstancesMatchServerInstances("GO-AutoSync Off", ParentingAutoSyncManager.AuthorityInstance.GameObjectAutoSyncOffTransforms); + } + + private bool AllClientInstancesMatchServerInstances(string type, List serverInstances) + { + for (int i = 0; i < serverInstances.Count; i++) { - var serverTransformToTest = ParentingAutoSyncManager.ServerInstance.GameObjectAutoSyncOffTransforms[i]; - for (int j = 0; j < m_ClientNetworkManagers.Length; j++) + var serverTransformToTest = serverInstances[i]; + foreach (var client in m_ClientNetworkManagers) { - var clientRelativeAutoSyncManager = ParentingAutoSyncManager.ClientInstances[m_ClientNetworkManagers[j].LocalClientId]; - var clientTransformToTest = clientRelativeAutoSyncManager.GameObjectAutoSyncOffTransforms[i]; + if (client.LocalClient.IsSessionOwner) + { + continue; + } + var clientRelativeAutoSyncManager = ParentingAutoSyncManager.ClientInstances[client.LocalClientId]; + var clientTransformToTest = clientRelativeAutoSyncManager.GameObjectAutoSyncOnTransforms[i]; if (!Approximately(clientTransformToTest.position, serverTransformToTest.position)) { - m_ErrorValidationLog.AppendLine($"[Client-{clientRelativeAutoSyncManager.NetworkManager.LocalClientId}][GO-AutoSync Off] {nameof(NetworkObject)}-{clientRelativeAutoSyncManager.NetworkObjectId}'s position {clientRelativeAutoSyncManager.transform.position} does not equal the server-side position {serverTransformToTest.transform.position}"); + m_ErrorValidationLog.AppendLine($"[Client-{clientRelativeAutoSyncManager.NetworkManager.LocalClientId}][{type}] {nameof(NetworkObject)}-{clientRelativeAutoSyncManager.NetworkObjectId}'s position {clientRelativeAutoSyncManager.transform.position} does not equal the server-side position {serverTransformToTest.transform.position}"); return false; } if (!Approximately(clientTransformToTest.rotation, serverTransformToTest.rotation)) { - m_ErrorValidationLog.AppendLine($"[Client-{clientRelativeAutoSyncManager.NetworkManager.LocalClientId}][GO-AutoSync Off] {nameof(NetworkObject)}-{clientRelativeAutoSyncManager.NetworkObjectId}'s rotation {clientRelativeAutoSyncManager.transform.eulerAngles} does not equal the server-side position {serverTransformToTest.transform.eulerAngles}"); + m_ErrorValidationLog.AppendLine($"[Client-{clientRelativeAutoSyncManager.NetworkManager.LocalClientId}][{type}] {nameof(NetworkObject)}-{clientRelativeAutoSyncManager.NetworkObjectId}'s rotation {clientRelativeAutoSyncManager.transform.eulerAngles} does not equal the server-side position {serverTransformToTest.transform.eulerAngles}"); return false; } if (!Approximately(clientTransformToTest.localScale, serverTransformToTest.localScale)) { - m_ErrorValidationLog.AppendLine($"[Client-{clientRelativeAutoSyncManager.NetworkManager.LocalClientId}][GO-AutoSync Off] {nameof(NetworkObject)}-{clientRelativeAutoSyncManager.NetworkObjectId}'s scale {clientRelativeAutoSyncManager.transform.localScale} does not equal the server-side position {serverTransformToTest.transform.localScale}"); + m_ErrorValidationLog.AppendLine($"[Client-{clientRelativeAutoSyncManager.NetworkManager.LocalClientId}][{type}] {nameof(NetworkObject)}-{clientRelativeAutoSyncManager.NetworkObjectId}'s scale {clientRelativeAutoSyncManager.transform.localScale} does not equal the server-side position {serverTransformToTest.transform.localScale}"); return false; } } @@ -615,9 +557,10 @@ public IEnumerator InSceneNestedAutoSyncObjectTest() SceneManager.sceneLoaded += SceneManager_sceneLoaded; SceneManager.LoadScene(k_BaseSceneToLoad, LoadSceneMode.Additive); m_InitialClientsLoadedScene = false; - m_ServerNetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + var authority = GetAuthorityNetworkManager(); + authority.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; - var sceneEventStartedStatus = m_ServerNetworkManager.SceneManager.LoadScene(k_TestSceneToLoad, LoadSceneMode.Additive); + var sceneEventStartedStatus = authority.SceneManager.LoadScene(k_TestSceneToLoad, LoadSceneMode.Additive); Assert.True(sceneEventStartedStatus == SceneEventProgressStatus.Started, $"Failed to load scene {k_TestSceneToLoad} with a return status of {sceneEventStartedStatus}."); yield return WaitForConditionOrTimeOut(() => m_InitialClientsLoadedScene); AssertOnTimeout($"Timed out waiting for all clients to load scene {k_TestSceneToLoad}!"); From d0300a8107fcf688ee9288b9d9f006bf6e1917f6 Mon Sep 17 00:00:00 2001 From: Emma Date: Wed, 18 Jun 2025 14:18:27 -0400 Subject: [PATCH 02/10] Update changelog --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index c543c95f5f..af1289464f 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,6 +14,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed synchronizing the destroyGameObject parameter to clients for InScenePlaced network objects. (#3514) - Fixed distributed authority related issue where enabling the `NetworkObject.DestroyWithScene` would cause errors when a destroying non-authority instances due to loading (single mode) or unloading scene events. (#3500) ### Changed From dca9712a28e245d9c4d3776702a58a293697cf3d Mon Sep 17 00:00:00 2001 From: Emma Date: Mon, 23 Jun 2025 10:15:48 -0400 Subject: [PATCH 03/10] dotnet-fix --- .../Runtime/Messaging/Messages/DestroyObjectMessage.cs | 7 ++++--- .../Runtime/Spawning/NetworkSpawnManager.cs | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index 4268eb59d3..f37c9f95a4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -1,6 +1,5 @@ using System.Linq; using System.Runtime.CompilerServices; -using UnityEngine; namespace Unity.Netcode { @@ -67,7 +66,8 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { BytePacker.WriteValueBitPacked(writer, DeferredDespawnTick); } - } else if (targetVersion >= k_AllowDestroyGameInPlaced) + } + else if (targetVersion >= k_AllowDestroyGameInPlaced) { writer.WriteByteSafe(m_DestroyFlags); } @@ -99,7 +99,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int { ByteUnpacker.ReadValueBitPacked(reader, out DeferredDespawnTick); } - } else if (receivedMessageVersion >= k_AllowDestroyGameInPlaced) + } + else if (receivedMessageVersion >= k_AllowDestroyGameInPlaced) { reader.ReadByteSafe(out m_DestroyFlags); } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 030c458655..374d537845 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1699,7 +1699,8 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec m_TargetClientIds.Add(NetworkManager.ServerClientId); } // Otherwise send to the clients for which the object is visible - else { + else + { foreach (var clientId in NetworkManager.ConnectedClientsIds) { if ((distributedAuthority && clientId == networkObject.OwnerClientId) || clientId == NetworkManager.LocalClientId) From cc817cf60a456189fb663c7d8ac8b2b5da273236 Mon Sep 17 00:00:00 2001 From: Emma Date: Mon, 23 Jun 2025 15:45:18 -0400 Subject: [PATCH 04/10] InScenePlacedNetworkObjectTests rework --- .../Runtime/Spawning/NetworkSpawnManager.cs | 2 +- .../InScenePlacedNetworkObjectBase.cs | 184 ++++++++ .../InScenePlacedNetworkObjectBase.cs.meta | 3 + .../InScenePlacedNetworkObjectDestroyTests.cs | 92 ++-- ...nScenePlacedNetworkObjectParentingTests.cs | 204 +++++++++ ...ePlacedNetworkObjectParentingTests.cs.meta | 3 + .../InScenePlacedNetworkObjectTests.cs | 406 +----------------- 7 files changed, 439 insertions(+), 455 deletions(-) create mode 100644 testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectBase.cs create mode 100644 testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectBase.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectParentingTests.cs create mode 100644 testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectParentingTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 374d537845..e706c56d4d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1621,7 +1621,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec return; } - if (destroyGameObject && networkObject.IsSceneObject == true && NetworkLog.CurrentLogLevel <= LogLevel.Normal) + if (destroyGameObject && networkObject.IsSceneObject == true && NetworkLog.CurrentLogLevel <= LogLevel.Developer) { Debug.LogWarning("Destroying in-scene network objects can lead to unexpected behavior. It is recommended to use NetworkObject.Despawn(false) instead."); } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectBase.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectBase.cs new file mode 100644 index 0000000000..1bbe4d92ca --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectBase.cs @@ -0,0 +1,184 @@ +using System.Collections; +using System.Linq; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine.SceneManagement; + +namespace TestProject.RuntimeTests +{ + public class InScenePlacedNetworkObjectBase : IntegrationTestWithApproximation + { + protected override int NumberOfClients => 2; + + internal const string SceneToLoad = "InSceneNetworkObject"; + private Scene m_AuthoritySideSceneLoaded; + private Scene m_PreviousSceneLoaded; + + protected InScenePlacedNetworkObjectBase(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } + + // Constructor that is used by InScenePlacedNetworkObjectDestroyTests + protected InScenePlacedNetworkObjectBase(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } + + protected override IEnumerator OnSetup() + { + NetworkObjectTestComponent.Reset(); + NetworkObjectTestComponent.VerboseDebug = m_EnableVerboseDebug; + m_AuthoritySideSceneLoaded = default; + return base.OnSetup(); + } + + /// + /// Very important to always have a backup "unloading" catch + /// in the event your test fails it could not potentially unload + /// a scene and the proceeding tests could be impacted by this! + /// + /// + protected override IEnumerator OnTearDown() + { + GetAuthorityNetworkManager().SceneManager.OnSceneEvent -= OnSceneEvent; + yield return CleanUpLoadedScene(); + } + + protected override IEnumerator OnStartedServerAndClients() + { + GetAuthorityNetworkManager().SceneManager.OnSceneEvent += OnSceneEvent; + return base.OnStartedServerAndClients(); + } + + /// + /// Gets the currently loaded scene. + /// If multiple scenes are loaded this will get the most recently loaded scene + /// + internal Scene GetLoadedScene() + { + return m_AuthoritySideSceneLoaded; + } + + private void OnSceneEvent(SceneEvent sceneEvent) + { + switch (sceneEvent.SceneEventType) + { + case SceneEventType.Load: + // Reset the loaded scene when the load starts to avoid data leaking + // m_AuthoritySideSceneLoaded tracks the most recently loaded scene. + // Any existing scene is no longer valid when a new scene begins loading. + m_AuthoritySideSceneLoaded = default; + break; + case SceneEventType.LoadComplete: + if (sceneEvent.ClientId == GetAuthorityNetworkManager().LocalClientId && sceneEvent.Scene.IsValid() && sceneEvent.Scene.isLoaded) + { + m_AuthoritySideSceneLoaded = sceneEvent.Scene; + } + break; + case SceneEventType.Unload: + m_PreviousSceneLoaded = m_AuthoritySideSceneLoaded; + m_AuthoritySideSceneLoaded = default; + break; + } + } + + private IEnumerator CleanUpLoadedScene() + { + if (m_AuthoritySideSceneLoaded.IsValid() && m_AuthoritySideSceneLoaded.isLoaded) + { + VerboseDebug($"Cleaning up loaded scene [{m_AuthoritySideSceneLoaded.name}-{m_AuthoritySideSceneLoaded.handle}]"); + var authority = GetAuthorityNetworkManager(); + authority.SceneManager.UnloadScene(m_AuthoritySideSceneLoaded); + yield return WaitForConditionOrTimeOut(() => m_ClientNetworkManagers.Any(c => c.IsListening)); + AssertOnTimeout($"[CleanUpLoadedScene] Timed out waiting for all in-scene instances to be despawned! Current spawned count: {m_ClientNetworkManagers.Count(c => !c.IsListening)}"); + } + } + + /// + /// Checks if all clients have loaded the most recently loaded scene. + /// + internal bool HaveAllClientsLoadedScene() + { + if (!(m_AuthoritySideSceneLoaded.IsValid() && m_AuthoritySideSceneLoaded.isLoaded)) + { + return false; + } + + foreach (var manager in m_NetworkManagers) + { + // default will have isLoaded as false so we can get the scene or default and test on isLoaded + var loadedScene = manager.SceneManager.ScenesLoaded.Values.FirstOrDefault(scene => scene.name == m_AuthoritySideSceneLoaded.name); + if (!loadedScene.isLoaded) + { + return false; + } + + if (manager.SceneManager.SceneEventProgressTracking.Count > 0) + { + return false; + } + } + + return true; + } + + /// + /// Checks if all clients have unloaded the most recently loaded scene + /// + internal bool HaveAllClientsUnloadedScene() + { + if (m_AuthoritySideSceneLoaded.IsValid() || m_AuthoritySideSceneLoaded.isLoaded) + { + return false; + } + + foreach (var manager in m_NetworkManagers) + { + if (manager.SceneManager.ScenesLoaded.Values.Any(scene => scene.name == m_PreviousSceneLoaded.name)) + { + return false; + } + + if (manager.SceneManager.SceneEventProgressTracking.Count > 0) + { + return false; + } + } + return true; + } + + + internal bool HaveAllClientsDespawnedInSceneObject() + { + // Make sure we despawned all instances + if (NetworkObjectTestComponent.DespawnedInstances.Count < TotalClients) + { + return false; + } + + foreach (var despawnedInstance in NetworkObjectTestComponent.DespawnedInstances) + { + if (despawnedInstance && despawnedInstance.gameObject && despawnedInstance.gameObject.activeInHierarchy) + { + return false; + } + } + + return true; + } + + internal bool HaveAllClientsSpawnedInSceneObject() + { + // Make sure we despawned all instances + if (NetworkObjectTestComponent.SpawnedInstances.Count < TotalClients) + { + return false; + } + + foreach (var despawnedInstance in NetworkObjectTestComponent.SpawnedInstances) + { + if (!despawnedInstance.gameObject.activeInHierarchy) + { + return false; + } + } + + return true; + } + } +} diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectBase.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectBase.cs.meta new file mode 100644 index 0000000000..65af909c98 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectBase.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ccb51483d67241dab57bcadad12d6ca1 +timeCreated: 1750696694 \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs index c5da158cc9..a362b30e5e 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs @@ -2,23 +2,24 @@ using System.Linq; using NUnit.Framework; using Unity.Netcode; -using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TestTools; namespace TestProject.RuntimeTests { + /// + /// These tests extend off InScenePlacedNetworkObjectTests. + /// Extending allows the DeferDespawn tests to not be created for client-server topology. + /// These tests specifically test destroying and despawning in-scene placed network objects. + /// [TestFixture(NetworkTopologyTypes.DistributedAuthority, DespawnMode.Despawn)] [TestFixture(NetworkTopologyTypes.DistributedAuthority, DespawnMode.DeferDespawn)] [TestFixture(NetworkTopologyTypes.ClientServer, DespawnMode.Despawn)] - public class InScenePlacedNetworkObjectDestroyTests : IntegrationTestWithApproximation + public class InScenePlacedNetworkObjectDestroyTests : InScenePlacedNetworkObjectBase { protected override int NumberOfClients => 2; - private const string k_SceneToLoad = "InSceneNetworkObject"; - private Scene m_ServerSideSceneLoaded; - - // private string m_SceneLoading = k_SceneToLoad; private readonly DespawnMode m_DespawnMode; public InScenePlacedNetworkObjectDestroyTests(NetworkTopologyTypes networkTopologyType, DespawnMode despawnMode) : base(networkTopologyType) @@ -26,24 +27,6 @@ public InScenePlacedNetworkObjectDestroyTests(NetworkTopologyTypes networkTopolo m_DespawnMode = despawnMode; } - protected override IEnumerator OnSetup() - { - NetworkObjectTestComponent.VerboseDebug = m_EnableVerboseDebug; - return base.OnSetup(); - } - - /// - /// Very important to always have a backup "unloading" catch - /// in the event your test fails it could not potentially unload - /// a scene and the proceeding tests could be impacted by this! - /// - /// - protected override IEnumerator OnTearDown() - { - NetworkObjectTestComponent.Reset(); - yield return CleanUpLoadedScene(); - } - public enum DespawnMode { Despawn, @@ -53,7 +36,14 @@ public enum DespawnMode private enum DestroyMode { DestroyGameObject, - DontDestroyGameObject, + DespawnGameObject, + } + + private NetworkObject m_JoinedClientDespawnedNetworkObject; + private void OnInSceneObjectDespawned(NetworkObject networkObject) + { + m_JoinedClientDespawnedNetworkObject = networkObject; + NetworkObjectTestComponent.OnInSceneObjectDespawned -= OnInSceneObjectDespawned; } /// @@ -76,17 +66,16 @@ private IEnumerator LoadSceneAndDespawnObject(DestroyMode destroyMode) var authority = GetAuthorityNetworkManager(); var destroyGameObject = destroyMode == DestroyMode.DestroyGameObject; - authority.SceneManager.OnSceneEvent += Server_OnSceneEvent; VerboseDebug("Loading scene"); - var status = authority.SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Additive); - Assert.IsTrue(status == SceneEventProgressStatus.Started, $"When attempting to load scene {k_SceneToLoad} was returned the following progress status: {status}"); + var status = authority.SceneManager.LoadScene(SceneToLoad, LoadSceneMode.Additive); + Assert.IsTrue(status == SceneEventProgressStatus.Started, $"When attempting to load scene {SceneToLoad} was returned the following progress status: {status}"); // This verifies the scene loaded and the in-scene placed NetworkObjects spawned. - yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients); + yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedInSceneObject); AssertOnTimeout($"Timed out waiting for total spawned in-scene placed NetworkObjects to reach a count of {TotalClients} and is currently {NetworkObjectTestComponent.SpawnedInstances.Count}"); - yield return WaitForConditionOrTimeOut(() => m_ServerSideSceneLoaded.IsValid() && m_ServerSideSceneLoaded.isLoaded); - AssertOnTimeout($"Timed out waiting for server to finish loading scene {k_SceneToLoad}!"); + yield return WaitForConditionOrTimeOut(HaveAllClientsLoadedScene); + AssertOnTimeout($"Timed out waiting for all clients to finish loading scene {SceneToLoad}!"); // Get the server-side instance of the in-scene NetworkObject Assert.True(s_GlobalNetworkObjects.ContainsKey(authority.LocalClientId), "Could not find server instance of the test in-scene NetworkObject!"); @@ -96,7 +85,7 @@ private IEnumerator LoadSceneAndDespawnObject(DestroyMode destroyMode) Assert.IsNotNull(serverObject, "Could not find server-side in-scene placed NetworkObject!"); Assert.IsTrue(serverObject.IsSpawned, $"{serverObject.name} is not spawned!"); - VerboseDebug("Doing despawn"); + VerboseDebug($"Doing despawn. destroyGameObject: {destroyGameObject}"); // Despawn the in-scene placed NetworkObject if (m_DespawnMode == DespawnMode.Despawn) { @@ -110,10 +99,9 @@ private IEnumerator LoadSceneAndDespawnObject(DestroyMode destroyMode) yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == 0); AssertOnTimeout($"Timed out waiting for all in-scene instances to be despawned! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()}"); - foreach (var manager in m_NetworkManagers) { - Assert.False(manager.SpawnManager.SpawnedObjects.ContainsKey(serverObjectId)); + Assert.False(manager.SpawnManager.SpawnedObjects.ContainsKey(serverObjectId), $"Client-{manager.LocalClientId} still has in-scene instance spawned!"); } foreach (var spawnedObject in spawnedObjects) @@ -139,7 +127,7 @@ private IEnumerator LoadSceneAndDespawnObject(DestroyMode destroyMode) [UnityTest] public IEnumerator InSceneNetworkObjectDespawnSyncAndSpawn() { - yield return LoadSceneAndDespawnObject(DestroyMode.DontDestroyGameObject); + yield return LoadSceneAndDespawnObject(DestroyMode.DespawnGameObject); var serverObject = NetworkObjectTestComponent.ServerNetworkObjectInstance; @@ -163,7 +151,7 @@ public IEnumerator InSceneNetworkObjectDespawnSyncAndSpawn() // Now test that the despawned in-scene placed NetworkObject can be re-spawned (without having been registered as a NetworkPrefab) serverObject.Spawn(); - yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients); + yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedInSceneObject); AssertOnTimeout($"Timed out waiting for all in-scene instances to be spawned! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {TotalClients}"); VerboseDebug("Network hiding object on first client"); @@ -181,38 +169,8 @@ public IEnumerator InSceneNetworkObjectDespawnSyncAndSpawn() // Validate that the first client can spawn the "netcode hidden" in-scene placed NetworkObject serverObject.NetworkShow(firstClientId); - yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients); + yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedInSceneObject); AssertOnTimeout($"[NetworkShow] Timed out waiting for Client-{firstClientId} to spawn the in-scene placed NetworkObject! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {TotalClients}"); - - yield return CleanUpLoadedScene(); - } - - private NetworkObject m_JoinedClientDespawnedNetworkObject; - - private void OnInSceneObjectDespawned(NetworkObject networkObject) - { - m_JoinedClientDespawnedNetworkObject = networkObject; - NetworkObjectTestComponent.OnInSceneObjectDespawned -= OnInSceneObjectDespawned; - } - - private void Server_OnSceneEvent(SceneEvent sceneEvent) - { - if (sceneEvent.ClientId == GetAuthorityNetworkManager().LocalClientId && sceneEvent.SceneEventType == SceneEventType.LoadComplete - && sceneEvent.Scene.IsValid() && sceneEvent.Scene.isLoaded) - { - m_ServerSideSceneLoaded = sceneEvent.Scene; - GetAuthorityNetworkManager().SceneManager.OnSceneEvent -= Server_OnSceneEvent; - } - } - - private IEnumerator CleanUpLoadedScene() - { - if (m_ServerSideSceneLoaded.IsValid() && m_ServerSideSceneLoaded.isLoaded) - { - GetAuthorityNetworkManager().SceneManager.UnloadScene(m_ServerSideSceneLoaded); - yield return WaitForConditionOrTimeOut(() => m_ClientNetworkManagers.Any(c => c.IsListening)); - AssertOnTimeout($"[CleanUpLoadedScene] Timed out waiting for all in-scene instances to be despawned! Current spawned count: {m_ClientNetworkManagers.Count(c => !c.IsListening)}"); - } } } } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectParentingTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectParentingTests.cs new file mode 100644 index 0000000000..2fffe07156 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectParentingTests.cs @@ -0,0 +1,204 @@ +using System.Collections; +using System.Linq; +using NUnit.Framework; +using Unity.Netcode; +using Unity.Netcode.Components; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; + +namespace TestProject.RuntimeTests +{ + [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)] + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)] + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Server)] + public class InScenePlacedNetworkObjectParentingTests : InScenePlacedNetworkObjectBase + { + protected override int NumberOfClients => 2; + + private const string k_InSceneUnder = "InSceneUnderGameObject"; + private const string k_InSceneUnderWithNetworkTransform = "InSceneUnderGameObjectWithNT"; + + public InScenePlacedNetworkObjectParentingTests(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } + + [UnityTest] + public IEnumerator ParentedInSceneObjectLateJoiningClient() + { + NetworkObjectTestComponent.ServerNetworkObjectInstance = null; + + var authority = GetAuthorityNetworkManager(); + var firstClient = GetNonAuthorityNetworkManager(); + NetworkManager secondClient = m_NetworkManagers.FirstOrDefault(manager => manager != authority && manager != firstClient); + Assert.IsNotNull(secondClient); + + authority.SceneManager.LoadScene(SceneToLoad, LoadSceneMode.Additive); + + yield return WaitForConditionOrTimeOut(HaveAllClientsLoadedScene); + AssertOnTimeout($"Timed out waiting for {SceneToLoad} scene to be loaded!"); + + var serverInSceneObjectInstance = NetworkObjectTestComponent.ServerNetworkObjectInstance; + Assert.IsNotNull(serverInSceneObjectInstance, $"Could not get the server-side registration of {nameof(NetworkObjectTestComponent)}!"); + + var firstClientInSceneObjectInstance = NetworkObjectTestComponent.SpawnedInstances.FirstOrDefault(c => c.NetworkManager == firstClient); + Assert.IsNotNull(firstClientInSceneObjectInstance, $"Could not get the client-side registration of {nameof(NetworkObjectTestComponent)}!"); + + Assert.IsTrue(firstClientInSceneObjectInstance.NetworkManager == firstClient); + + var playerObjectToParent = m_UseHost ? authority.LocalClient.PlayerObject : m_PlayerNetworkObjects[authority.LocalClientId][secondClient.LocalClientId]; + NetworkObject clientSidePlayer; + if (!m_UseHost) + { + playerObjectToParent = m_PlayerNetworkObjects[authority.LocalClientId][secondClient.LocalClientId]; + clientSidePlayer = m_PlayerNetworkObjects[firstClient.LocalClientId][secondClient.LocalClientId]; + } + else + { + clientSidePlayer = m_PlayerNetworkObjects[firstClient.LocalClientId][authority.LocalClientId]; + } + + // Parent the object + serverInSceneObjectInstance.transform.parent = playerObjectToParent.transform; + + yield return WaitForConditionOrTimeOut(() => firstClientInSceneObjectInstance.transform.parent != null && firstClientInSceneObjectInstance.transform.parent == clientSidePlayer.transform); + AssertOnTimeout($"Timed out waiting for the client-side id ({authority.LocalClientId}) server player transform to be set on the client-side in-scene object!"); + + // Now late join a client + var lateJoinClient = CreateNewClient(); + yield return StartClient(lateJoinClient); + + var lateJoinClientInSceneObjectInstance = NetworkObjectTestComponent.SpawnedInstances.FirstOrDefault(c => c.NetworkManager == lateJoinClient); + Assert.IsNotNull(lateJoinClientInSceneObjectInstance, $"Could not get the client-side registration of {nameof(NetworkObjectTestComponent)} for the late joining client!"); + + // Now get the late-joining client's instance for the server player + clientSidePlayer = m_PlayerNetworkObjects[lateJoinClient.LocalClientId][clientSidePlayer.OwnerClientId]; + + // Validate the late joined client's in-scene NetworkObject is parented to the server-side player + yield return WaitForConditionOrTimeOut(() => lateJoinClientInSceneObjectInstance.transform.parent != null && lateJoinClientInSceneObjectInstance.transform.parent == clientSidePlayer.transform); + AssertOnTimeout($"Timed out waiting for the client-side id ({firstClient.LocalClientId}) player transform to be set on the client-side in-scene object!"); + } + + public enum ParentSyncSettings + { + ParentSync, + NoParentSync + } + + public enum TransformSyncSettings + { + TransformSync, + NoTransformSync + } + + public enum TransformSpace + { + World, + Local + } + + /// + /// This test validates the initial synchronization of an in-scene placed NetworkObject parented + /// underneath a GameObject. There are two scenes for this tests where the child NetworkObject does + /// and does not have a NetworkTransform component. + /// + /// Scene to load + /// settings + /// settings + /// setting (when available) + [UnityTest] + public IEnumerator ParentedInSceneObjectUnderGameObject([Values(k_InSceneUnder, k_InSceneUnderWithNetworkTransform)] string inSceneUnderToLoad, + [Values] ParentSyncSettings parentSyncSettings, [Values] TransformSyncSettings transformSyncSettings, [Values] TransformSpace transformSpace) + { + var useNetworkTransform = inSceneUnderToLoad == k_InSceneUnderWithNetworkTransform; + + // Note: This test is a modified copy of ParentedInSceneObjectLateJoiningClient. + // The 1st client is being ignored in this test and the focus is primarily on the late joining + // 2nd client after adjustments have been made to the child NetworkBehaviour and if applicable + // NetworkTransform. + + NetworkObjectTestComponent.ServerNetworkObjectInstance = null; + + var authority = GetAuthorityNetworkManager(); + var nonAuthority = GetNonAuthorityNetworkManager(0); + var secondClient = GetNonAuthorityNetworkManager(1); + + authority.SceneManager.LoadScene(inSceneUnderToLoad, LoadSceneMode.Additive); + + yield return WaitForConditionOrTimeOut(HaveAllClientsLoadedScene); + AssertOnTimeout($"Timed out waiting for {SceneToLoad} scene to be loaded!"); + + var serverInSceneObjectInstance = NetworkObjectTestComponent.ServerNetworkObjectInstance; + Assert.IsNotNull(serverInSceneObjectInstance, $"Could not get the server-side registration of {nameof(NetworkObjectTestComponent)}!"); + var firstClientInSceneObjectInstance = NetworkObjectTestComponent.SpawnedInstances.FirstOrDefault(c => c.NetworkManager == nonAuthority); + Assert.IsNotNull(firstClientInSceneObjectInstance, $"Could not get the client-side registration of {nameof(NetworkObjectTestComponent)}!"); + Assert.IsTrue(firstClientInSceneObjectInstance.NetworkManager == nonAuthority); + + serverInSceneObjectInstance.AutoObjectParentSync = parentSyncSettings == ParentSyncSettings.ParentSync; + serverInSceneObjectInstance.SynchronizeTransform = transformSyncSettings == TransformSyncSettings.TransformSync; + + var serverNetworkTransform = useNetworkTransform ? serverInSceneObjectInstance.GetComponent() : null; + if (useNetworkTransform) + { + serverNetworkTransform.InLocalSpace = transformSpace == TransformSpace.Local; + } + + // Now late join a client + yield return CreateAndStartNewClient(); + + var lateJoinClientInSceneObjectInstance = NetworkObjectTestComponent.SpawnedInstances.FirstOrDefault(c => c.NetworkManager == secondClient); + Assert.IsNotNull(lateJoinClientInSceneObjectInstance, $"Could not get the client-side registration of {nameof(NetworkObjectTestComponent)} for the late joining client!"); + + // Now make sure the server and newly joined client transform values match. + RotationsMatch(serverInSceneObjectInstance.transform, lateJoinClientInSceneObjectInstance.transform, transformSpace == TransformSpace.Local); + PositionsMatch(serverInSceneObjectInstance.transform, lateJoinClientInSceneObjectInstance.transform, transformSpace == TransformSpace.Local); + // When testing local space we also do a sanity check and validate the world space values too. + if (transformSpace == TransformSpace.Local) + { + RotationsMatch(serverInSceneObjectInstance.transform, lateJoinClientInSceneObjectInstance.transform); + PositionsMatch(serverInSceneObjectInstance.transform, lateJoinClientInSceneObjectInstance.transform); + } + ScaleValuesMatch(serverInSceneObjectInstance.transform, lateJoinClientInSceneObjectInstance.transform); + } + + private void RotationsMatch(Transform transformA, Transform transformB, bool inLocalSpace = false) + { + var authorityEulerRotation = inLocalSpace ? transformA.localRotation.eulerAngles : transformA.rotation.eulerAngles; + var nonAuthorityEulerRotation = inLocalSpace ? transformB.localRotation.eulerAngles : transformB.rotation.eulerAngles; + var xIsEqual = ApproximatelyEuler(authorityEulerRotation.x, nonAuthorityEulerRotation.x); + var yIsEqual = ApproximatelyEuler(authorityEulerRotation.y, nonAuthorityEulerRotation.y); + var zIsEqual = ApproximatelyEuler(authorityEulerRotation.z, nonAuthorityEulerRotation.z); + + VerboseDebug($"[{transformA.gameObject.name}][X-{xIsEqual} | Y-{yIsEqual} | Z-{zIsEqual}] " + + $"Authority rotation {authorityEulerRotation} != [{transformB.gameObject.name}] NonAuthority rotation {nonAuthorityEulerRotation}"); + + Assert.True(xIsEqual && yIsEqual && zIsEqual, $"[{transformA.gameObject.name}][X-{xIsEqual} | Y-{yIsEqual} | Z-{zIsEqual}]" + + $"Authority rotation {authorityEulerRotation} != [{transformB.gameObject.name}] NonAuthority rotation {nonAuthorityEulerRotation}"); + } + + private void PositionsMatch(Transform transformA, Transform transformB, bool inLocalSpace = false) + { + var authorityPosition = inLocalSpace ? transformA.localPosition : transformA.position; + var nonAuthorityPosition = inLocalSpace ? transformB.localPosition : transformB.position; + var xIsEqual = Approximately(authorityPosition.x, nonAuthorityPosition.x); + var yIsEqual = Approximately(authorityPosition.y, nonAuthorityPosition.y); + var zIsEqual = Approximately(authorityPosition.z, nonAuthorityPosition.z); + + VerboseDebug($"[{transformA.gameObject.name}] Authority position {authorityPosition} != [{transformB.gameObject.name}] NonAuthority position {nonAuthorityPosition}"); + + Assert.True(xIsEqual && yIsEqual && zIsEqual, $"[{transformA.gameObject.name}] Authority position {authorityPosition} != [{transformB.gameObject.name}] NonAuthority position {nonAuthorityPosition}"); + } + + private void ScaleValuesMatch(Transform transformA, Transform transformB) + { + var authorityScale = transformA.localScale; + var nonAuthorityScale = transformB.localScale; + var xIsEqual = Approximately(authorityScale.x, nonAuthorityScale.x); + var yIsEqual = Approximately(authorityScale.y, nonAuthorityScale.y); + var zIsEqual = Approximately(authorityScale.z, nonAuthorityScale.z); + + VerboseDebug($"[{transformA.gameObject.name}] Authority scale {authorityScale} == [{transformB.gameObject.name}] NonAuthority scale {nonAuthorityScale}"); + + Assert.True(xIsEqual && yIsEqual && zIsEqual, $"[{transformA.gameObject.name}] Authority scale {authorityScale} != [{transformB.gameObject.name}] NonAuthority scale {nonAuthorityScale}"); + } + + } +} diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectParentingTests.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectParentingTests.cs.meta new file mode 100644 index 0000000000..3b5b0cfa11 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectParentingTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: db9379f3596d4b6da4f3635ec65e9d70 +timeCreated: 1750697127 \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs index 55bb3d75b8..88d3504552 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs @@ -1,9 +1,6 @@ using System.Collections; -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using Unity.Netcode; -using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.SceneManagement; @@ -16,202 +13,15 @@ namespace TestProject.RuntimeTests [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)] [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)] [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Server)] - public class InScenePlacedNetworkObjectTests : IntegrationTestWithApproximation + public class InScenePlacedNetworkObjectTests : InScenePlacedNetworkObjectBase { protected override int NumberOfClients => 2; - private const string k_SceneToLoad = "InSceneNetworkObject"; - private const string k_InSceneUnder = "InSceneUnderGameObject"; - private const string k_InSceneUnderWithNT = "InSceneUnderGameObjectWithNT"; - private Scene m_ServerSideSceneLoaded; - private string m_SceneLoading = k_SceneToLoad; - private NetworkManager m_LateJoinClient; - public InScenePlacedNetworkObjectTests(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } - protected override IEnumerator OnSetup() - { - NetworkObjectTestComponent.Reset(); - NetworkObjectTestComponent.VerboseDebug = m_EnableVerboseDebug; - return base.OnSetup(); - } - - /// - /// Very important to always have a backup "unloading" catch - /// in the event your test fails it could not potentially unload - /// a scene and the proceeding tests could be impacted by this! - /// - /// - protected override IEnumerator OnTearDown() - { - m_LateJoinClient = null; - yield return CleanUpLoadedScene(); - } - - protected override void OnNewClientCreated(NetworkManager networkManager) - { - m_LateJoinClient = networkManager; - base.OnNewClientCreated(networkManager); - } - - private Scene m_ClientLoadedScene; - - [UnityTest] - public IEnumerator ParentedInSceneObjectLateJoiningClient() - { - NetworkObjectTestComponent.ServerNetworkObjectInstance = null; - - var authority = GetAuthorityNetworkManager(); - var firstClient = GetNonAuthorityNetworkManager(); - NetworkManager secondClient = m_NetworkManagers.FirstOrDefault(manager => manager != authority && manager != firstClient); - Assert.IsNotNull(secondClient); - - firstClient.SceneManager.OnSceneEvent += OnSceneEvent; - authority.SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Additive); - - yield return WaitForConditionOrTimeOut(() => m_ClientLoadedScene.IsValid() && m_ClientLoadedScene.isLoaded); - AssertOnTimeout($"Timed out waiting for {k_SceneToLoad} scene to be loaded!"); - - firstClient.SceneManager.OnSceneEvent -= OnSceneEvent; - - var serverInSceneObjectInstance = NetworkObjectTestComponent.ServerNetworkObjectInstance; - Assert.IsNotNull(serverInSceneObjectInstance, $"Could not get the server-side registration of {nameof(NetworkObjectTestComponent)}!"); - - var firstClientInSceneObjectInstance = NetworkObjectTestComponent.SpawnedInstances.FirstOrDefault(c => c.NetworkManager == firstClient); - Assert.IsNotNull(firstClientInSceneObjectInstance, $"Could not get the client-side registration of {nameof(NetworkObjectTestComponent)}!"); - - Assert.IsTrue(firstClientInSceneObjectInstance.NetworkManager == firstClient); - - var playerObjectToParent = m_UseHost ? authority.LocalClient.PlayerObject : m_PlayerNetworkObjects[authority.LocalClientId][secondClient.LocalClientId]; - var clientSidePlayer = (NetworkObject)null; - if (!m_UseHost) - { - playerObjectToParent = m_PlayerNetworkObjects[authority.LocalClientId][secondClient.LocalClientId]; - clientSidePlayer = m_PlayerNetworkObjects[firstClient.LocalClientId][secondClient.LocalClientId]; - } - else - { - clientSidePlayer = m_PlayerNetworkObjects[firstClient.LocalClientId][authority.LocalClientId]; - } - - // Parent the object - serverInSceneObjectInstance.transform.parent = playerObjectToParent.transform; - - yield return WaitForConditionOrTimeOut(() => firstClientInSceneObjectInstance.transform.parent != null && firstClientInSceneObjectInstance.transform.parent == clientSidePlayer.transform); - AssertOnTimeout($"Timed out waiting for the client-side id ({authority.LocalClientId}) server player transform to be set on the client-side in-scene object!"); - - // Now late join a client - yield return CreateAndStartNewClient(); - yield return WaitForConditionOrTimeOut(() => (m_LateJoinClient.IsConnectedClient && m_LateJoinClient.IsListening)); - AssertOnTimeout($"Timed out waiting for {m_LateJoinClient.name} to reconnect!"); - - yield return s_DefaultWaitForTick; - - // Update the newly joined client information - ClientNetworkManagerPostStartInit(); - - var lateJoinClientInSceneObjectInstance = NetworkObjectTestComponent.SpawnedInstances.FirstOrDefault(c => c.NetworkManager == m_LateJoinClient); - Assert.IsNotNull(lateJoinClientInSceneObjectInstance, $"Could not get the client-side registration of {nameof(NetworkObjectTestComponent)} for the late joining client!"); - - // Now get the late-joining client's instance for the server player - clientSidePlayer = m_PlayerNetworkObjects[m_LateJoinClient.LocalClientId][clientSidePlayer.OwnerClientId]; - - // Validate the late joined client's in-scene NetworkObject is parented to the server-side player - yield return WaitForConditionOrTimeOut(() => lateJoinClientInSceneObjectInstance.transform.parent != null && lateJoinClientInSceneObjectInstance.transform.parent == clientSidePlayer.transform); - AssertOnTimeout($"Timed out waiting for the client-side id ({firstClient.LocalClientId}) player transform to be set on the client-side in-scene object!"); - } - - private void OnSceneEvent(SceneEvent sceneEvent) - { - if (sceneEvent.SceneEventType == SceneEventType.LoadComplete && sceneEvent.SceneName == m_SceneLoading && sceneEvent.ClientId == GetNonAuthorityNetworkManager().LocalClientId) - { - m_ClientLoadedScene = sceneEvent.Scene; - } - } - - private NetworkObject m_JoinedClientDespawnedNetworkObject; - - private void OnInSceneObjectDespawned(NetworkObject networkObject) - { - m_JoinedClientDespawnedNetworkObject = networkObject; - NetworkObjectTestComponent.OnInSceneObjectDespawned -= OnInSceneObjectDespawned; - } - - private void Server_OnSceneEvent(SceneEvent sceneEvent) - { - if (sceneEvent.ClientId == GetAuthorityNetworkManager().LocalClientId && sceneEvent.SceneEventType == SceneEventType.LoadComplete - && sceneEvent.Scene.IsValid() && sceneEvent.Scene.isLoaded) - { - m_ServerSideSceneLoaded = sceneEvent.Scene; - GetAuthorityNetworkManager().SceneManager.OnSceneEvent -= Server_OnSceneEvent; - } - } - - private IEnumerator CleanUpLoadedScene() - { - if (m_ServerSideSceneLoaded.IsValid() && m_ServerSideSceneLoaded.isLoaded) - { - var authority = GetAuthorityNetworkManager(); - authority.SceneManager.OnSceneEvent += Unload_OnSceneEvent; - authority.SceneManager.UnloadScene(m_ServerSideSceneLoaded); - yield return WaitForConditionOrTimeOut(() => m_ClientNetworkManagers.Any(c => c.IsListening)); - AssertOnTimeout($"[CleanUpLoadedScene] Timed out waiting for all in-scene instances to be despawned! Current spawned count: {m_ClientNetworkManagers.Count(c => !c.IsListening)}"); - } - } - - private void Unload_OnSceneEvent(SceneEvent sceneEvent) - { - var authority = GetAuthorityNetworkManager(); - if (sceneEvent.ClientId == authority.LocalClientId && sceneEvent.SceneEventType == SceneEventType.UnloadEventCompleted) - { - authority.SceneManager.OnSceneEvent -= Unload_OnSceneEvent; - } - } - - private bool m_AllClientsLoadedScene; - private bool m_AllClientsUnloadedScene; private int m_NumberOfInstancesCheck; - private Scene m_SceneLoaded; - - private bool HaveAllClientsDespawnedInSceneObject() - { - // Make sure we despawned all instances - if (NetworkObjectTestComponent.DespawnedInstances.Count < m_NumberOfInstancesCheck) - { - return false; - } - - foreach (var despawnedInstance in NetworkObjectTestComponent.DespawnedInstances) - { - if (despawnedInstance && despawnedInstance.gameObject && despawnedInstance.gameObject.activeInHierarchy) - { - return false; - } - } - - return true; - } - - private bool HaveAllClientsSpawnedInSceneObject() - { - // Make sure we despawned all instances - if (NetworkObjectTestComponent.SpawnedInstances.Count < m_NumberOfInstancesCheck) - { - return false; - } - - foreach (var despawnedInstance in NetworkObjectTestComponent.SpawnedInstances) - { - if (!despawnedInstance.gameObject.activeInHierarchy) - { - return false; - } - } - - return true; - } /// /// This validates that users can despawn in-scene placed NetworkObjects and disable the @@ -225,18 +35,16 @@ private bool HaveAllClientsSpawnedInSceneObject() [UnityTest] public IEnumerator EnableDisableInSceneObjectTests() { + m_EnableVerboseDebug = true; NetworkObjectTestComponent.ServerNetworkObjectInstance = null; // Enabled disabling the NetworkObject when it is despawned NetworkObjectTestComponent.DisableOnDespawn = true; - // Set the number of instances to expect - m_NumberOfInstancesCheck = TotalClients; - var authority = GetAuthorityNetworkManager(); - authority.SceneManager.OnLoadEventCompleted += SceneManager_OnLoadEventCompleted; - authority.SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Additive); - yield return WaitForConditionOrTimeOut(() => m_AllClientsLoadedScene); - AssertOnTimeout($"Timed out waiting for {k_SceneToLoad} scene to be loaded on all clients!"); - authority.SceneManager.OnLoadEventCompleted -= SceneManager_OnLoadEventCompleted; + + authority.SceneManager.LoadScene(SceneToLoad, LoadSceneMode.Additive); + + yield return WaitForConditionOrTimeOut(HaveAllClientsLoadedScene); + AssertOnTimeout($"Timed out waiting for {SceneToLoad} scene to be loaded on all clients!"); // Verify all connected clients spawned the in-scene placed NetworkObject yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedInSceneObject); @@ -252,11 +60,9 @@ public IEnumerator EnableDisableInSceneObjectTests() AssertOnTimeout($"[Test #1] Timed out waiting for all instances to be despawned and disabled!"); // Test #2: Late-join a client and re-verify that all in-scene placed object instances are still disabled - yield return CreateAndStartNewClient(); + var newlyJoinedClient = CreateNewClient(); + yield return StartClient(newlyJoinedClient); - var newlyJoinedClient = m_LateJoinClient; - - m_NumberOfInstancesCheck++; yield return WaitForConditionOrTimeOut(HaveAllClientsDespawnedInSceneObject); AssertOnTimeout($"[Test #2] Timed out waiting for all instances to be despawned and disabled!"); @@ -269,16 +75,14 @@ public IEnumerator EnableDisableInSceneObjectTests() // also having the server-side disable the in-scene placed NetworkObject and verify all // connected clients completed the scene switch and that all in-scene placed NetworkObjects // are despawned and disabled. - m_AllClientsLoadedScene = false; - m_AllClientsUnloadedScene = false; NetworkObjectTestComponent.ServerNetworkObjectInstance = null; NetworkObjectTestComponent.DisableOnSpawn = true; - authority.SceneManager.OnUnloadEventCompleted += SceneManager_OnUnloadEventCompleted; - authority.SceneManager.UnloadScene(m_SceneLoaded); - yield return WaitForConditionOrTimeOut(() => m_AllClientsUnloadedScene); - AssertOnTimeout($"Timed out waiting for {k_SceneToLoad} scene to be unloaded on all clients!"); - authority.SceneManager.OnUnloadEventCompleted -= SceneManager_OnUnloadEventCompleted; + + authority.SceneManager.UnloadScene(GetLoadedScene()); + + yield return WaitForConditionOrTimeOut(HaveAllClientsUnloadedScene); + AssertOnTimeout($"Timed out waiting for {SceneToLoad} scene to be unloaded on all clients!"); // Verify the spawned instances list is empty Assert.True(NetworkObjectTestComponent.SpawnedInstances.Count == 0, $"There are {NetworkObjectTestComponent.SpawnedInstances.Count} that did not despawn when the scene was unloaded!"); @@ -287,11 +91,10 @@ public IEnumerator EnableDisableInSceneObjectTests() NetworkObjectTestComponent.DespawnedInstances.Clear(); // Now scene switch (LoadSceneMode.Single) into the scene with the in-scene placed NetworkObject we have been testing - authority.SceneManager.OnLoadEventCompleted += SceneManager_OnLoadEventCompleted; - authority.SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Single); - yield return WaitForConditionOrTimeOut(() => m_AllClientsLoadedScene); - AssertOnTimeout($"Timed out waiting for {k_SceneToLoad} scene to be loaded on all clients!"); - authority.SceneManager.OnLoadEventCompleted -= SceneManager_OnLoadEventCompleted; + authority.SceneManager.LoadScene(SceneToLoad, LoadSceneMode.Single); + + yield return WaitForConditionOrTimeOut(HaveAllClientsLoadedScene); + AssertOnTimeout($"Timed out waiting for {SceneToLoad} scene to be loaded on all clients!"); // Verify all client instances are disabled and despawned when done scene switching yield return WaitForConditionOrTimeOut(HaveAllClientsDespawnedInSceneObject); @@ -306,179 +109,8 @@ public IEnumerator EnableDisableInSceneObjectTests() // Verify all clients spawned their in-scene NetworkObject relative instance yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedInSceneObject); - AssertOnTimeout($"[Test #2] Timed out waiting for all instances to be enabled and spawned!"); - yield return StopOneClient(newlyJoinedClient, true); - - // Tests complete! - } - - private void SceneManager_OnUnloadEventCompleted(string sceneName, LoadSceneMode loadSceneMode, List clientsCompleted, List clientsTimedOut) - { - foreach (var clientId in clientsCompleted) - { - Assert.True(GetAuthorityNetworkManager().ConnectedClientsIds.Contains(clientId)); - } - m_AllClientsUnloadedScene = true; - } - - private void SceneManager_OnLoadEventCompleted(string sceneName, LoadSceneMode loadSceneMode, List clientsCompleted, List clientsTimedOut) - { - foreach (var clientId in clientsCompleted) - { - Assert.True(GetAuthorityNetworkManager().ConnectedClientsIds.Contains(clientId)); - } - m_AllClientsLoadedScene = true; - m_SceneLoaded = SceneManager.GetSceneByName(sceneName); - } - - public enum ParentSyncSettings - { - ParentSync, - NoParentSync + AssertOnTimeout("[Test #2] Timed out waiting for all instances to be enabled and spawned!"); } - - public enum TransformSyncSettings - { - TransformSync, - NoTransformSync - } - - public enum TransformSpace - { - World, - Local - } - - /// - /// This test validates the initial synchronization of an in-scene placed NetworkObject parented - /// underneath a GameObject. There are two scenes for this tests where the child NetworkObject does - /// and does not have a NetworkTransform component. - /// - /// Scene to load - /// settings - /// settings - /// setting (when available) - [UnityTest] - public IEnumerator ParentedInSceneObjectUnderGameObject([Values(k_InSceneUnder, k_InSceneUnderWithNT)] string inSceneUnderToLoad, - [Values] ParentSyncSettings parentSyncSettings, [Values] TransformSyncSettings transformSyncSettings, [Values] TransformSpace transformSpace) - { - var useNetworkTransform = m_SceneLoading == k_InSceneUnderWithNT; - - m_SceneLoading = inSceneUnderToLoad; - - // Note: This test is a modified copy of ParentedInSceneObjectLateJoiningClient. - // The 1st client is being ignored in this test and the focus is primarily on the late joining - // 2nd client after adjustments have been made to the child NetworkBehaviour and if applicable - // NetworkTransform. - - NetworkObjectTestComponent.ServerNetworkObjectInstance = null; - - var authority = GetAuthorityNetworkManager(); - var nonAuthority = GetNonAuthorityNetworkManager(); - var secondClient = m_ClientNetworkManagers.FirstOrDefault(c => c != authority && c != nonAuthority); - - nonAuthority.SceneManager.OnSceneEvent += OnSceneEvent; - authority.SceneManager.LoadScene(m_SceneLoading, LoadSceneMode.Additive); - yield return WaitForConditionOrTimeOut(() => m_ClientLoadedScene.IsValid() && m_ClientLoadedScene.isLoaded); - AssertOnTimeout($"Timed out waiting for {k_SceneToLoad} scene to be loaded!"); - - nonAuthority.SceneManager.OnSceneEvent -= OnSceneEvent; - var serverInSceneObjectInstance = NetworkObjectTestComponent.ServerNetworkObjectInstance; - Assert.IsNotNull(serverInSceneObjectInstance, $"Could not get the server-side registration of {nameof(NetworkObjectTestComponent)}!"); - var firstClientInSceneObjectInstance = NetworkObjectTestComponent.SpawnedInstances.FirstOrDefault(c => c.NetworkManager == nonAuthority); - Assert.IsNotNull(firstClientInSceneObjectInstance, $"Could not get the client-side registration of {nameof(NetworkObjectTestComponent)}!"); - Assert.IsTrue(firstClientInSceneObjectInstance.NetworkManager == nonAuthority); - - serverInSceneObjectInstance.AutoObjectParentSync = parentSyncSettings == ParentSyncSettings.ParentSync; - serverInSceneObjectInstance.SynchronizeTransform = transformSyncSettings == TransformSyncSettings.TransformSync; - - var serverNetworkTransform = useNetworkTransform ? serverInSceneObjectInstance.GetComponent() : null; - if (useNetworkTransform) - { - serverNetworkTransform.InLocalSpace = transformSpace == TransformSpace.Local; - } - - // Now late join a client - yield return CreateAndStartNewClient(); - yield return WaitForConditionOrTimeOut(() => (m_LateJoinClient.IsConnectedClient && m_LateJoinClient.IsListening)); - AssertOnTimeout($"Timed out waiting for {m_LateJoinClient.name} to reconnect!"); - - yield return s_DefaultWaitForTick; - - // Update the newly joined client information - ClientNetworkManagerPostStartInit(); - - var lateJoinClientInSceneObjectInstance = NetworkObjectTestComponent.SpawnedInstances.FirstOrDefault(c => c.NetworkManager == secondClient); - Assert.IsNotNull(lateJoinClientInSceneObjectInstance, $"Could not get the client-side registration of {nameof(NetworkObjectTestComponent)} for the late joining client!"); - - // Now make sure the server and newly joined client transform values match. - RotationsMatch(serverInSceneObjectInstance.transform, lateJoinClientInSceneObjectInstance.transform, transformSpace == TransformSpace.Local); - PositionsMatch(serverInSceneObjectInstance.transform, lateJoinClientInSceneObjectInstance.transform, transformSpace == TransformSpace.Local); - // When testing local space we also do a sanity check and validate the world space values too. - if (transformSpace == TransformSpace.Local) - { - RotationsMatch(serverInSceneObjectInstance.transform, lateJoinClientInSceneObjectInstance.transform); - PositionsMatch(serverInSceneObjectInstance.transform, lateJoinClientInSceneObjectInstance.transform); - } - ScaleValuesMatch(serverInSceneObjectInstance.transform, lateJoinClientInSceneObjectInstance.transform); - } - - protected bool RotationsMatch(Transform transformA, Transform transformB, bool inLocalSpace = false) - { - var authorityEulerRotation = inLocalSpace ? transformA.localRotation.eulerAngles : transformA.rotation.eulerAngles; - var nonAuthorityEulerRotation = inLocalSpace ? transformB.localRotation.eulerAngles : transformB.rotation.eulerAngles; - var xIsEqual = ApproximatelyEuler(authorityEulerRotation.x, nonAuthorityEulerRotation.x); - var yIsEqual = ApproximatelyEuler(authorityEulerRotation.y, nonAuthorityEulerRotation.y); - var zIsEqual = ApproximatelyEuler(authorityEulerRotation.z, nonAuthorityEulerRotation.z); - if (!xIsEqual || !yIsEqual || !zIsEqual) - { - VerboseDebug($"[{transformA.gameObject.name}][X-{xIsEqual} | Y-{yIsEqual} | Z-{zIsEqual}]" + - $"Authority rotation {authorityEulerRotation} != [{transformB.gameObject.name}] NonAuthority rotation {nonAuthorityEulerRotation}"); - } - else if (m_EnableVerboseDebug) - { - VerboseDebug($"[{transformA.gameObject.name}][X-{xIsEqual} | Y-{yIsEqual} | Z-{zIsEqual}] " + - $"Authority rotation {authorityEulerRotation} != [{transformB.gameObject.name}] NonAuthority rotation {nonAuthorityEulerRotation}"); - } - return xIsEqual && yIsEqual && zIsEqual; - } - - protected bool PositionsMatch(Transform transformA, Transform transformB, bool inLocalSpace = false) - { - var authorityPosition = inLocalSpace ? transformA.localPosition : transformA.position; - var nonAuthorityPosition = inLocalSpace ? transformB.localPosition : transformB.position; - var xIsEqual = Approximately(authorityPosition.x, nonAuthorityPosition.x); - var yIsEqual = Approximately(authorityPosition.y, nonAuthorityPosition.y); - var zIsEqual = Approximately(authorityPosition.z, nonAuthorityPosition.z); - if (!xIsEqual || !yIsEqual || !zIsEqual) - { - VerboseDebug($"[{transformA.gameObject.name}] Authority position {authorityPosition} != [{transformB.gameObject.name}] NonAuthority position {nonAuthorityPosition}"); - } - else if (m_EnableVerboseDebug) - { - VerboseDebug($"[{transformA.gameObject.name}] Authority position {authorityPosition} != [{transformB.gameObject.name}] NonAuthority position {nonAuthorityPosition}"); - } - return xIsEqual && yIsEqual && zIsEqual; - } - - protected bool ScaleValuesMatch(Transform transformA, Transform transformB) - { - var authorityScale = transformA.localScale; - var nonAuthorityScale = transformB.localScale; - var xIsEqual = Approximately(authorityScale.x, nonAuthorityScale.x); - var yIsEqual = Approximately(authorityScale.y, nonAuthorityScale.y); - var zIsEqual = Approximately(authorityScale.z, nonAuthorityScale.z); - if (!xIsEqual || !yIsEqual || !zIsEqual) - { - VerboseDebug($"[{transformA.gameObject.name}] Authority scale {authorityScale} != [{transformB.gameObject.name}] NonAuthority scale {nonAuthorityScale}"); - } - else if (m_EnableVerboseDebug) - { - VerboseDebug($"[{transformA.gameObject.name}] Authority scale {authorityScale} == [{transformB.gameObject.name}] NonAuthority scale {nonAuthorityScale}"); - } - return xIsEqual && yIsEqual && zIsEqual; - } - } [TestFixture(NetworkTopologyTypes.DistributedAuthority)] From 835e6bcc82d875defd395dd8e6e8a30eb509a515 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 23 Jun 2025 22:57:44 -0500 Subject: [PATCH 05/10] style removing using directive. --- .../InScenePlacedNetworkObjectDestroyTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs index a362b30e5e..6648a2605c 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs @@ -2,7 +2,6 @@ using System.Linq; using NUnit.Framework; using Unity.Netcode; -using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TestTools; From 2f5591ff9c5d88a9700a0660a831981e4b754bbc Mon Sep 17 00:00:00 2001 From: Emma Date: Wed, 25 Jun 2025 17:00:13 -0400 Subject: [PATCH 06/10] fixes --- .../SceneManagement/NetworkSceneManager.cs | 33 +++++++++++++++---- .../SceneManagement/SceneEventProgress.cs | 8 +++++ .../Runtime/Spawning/NetworkSpawnManager.cs | 16 ++++----- .../InScenePlacedNetworkObject.meta | 3 ++ .../InSceneObjectBase.cs} | 7 ++-- .../InSceneObjectBase.cs.meta} | 0 .../InSceneObjectDestroyTests.cs} | 14 ++++++-- .../InSceneObjectDestroyTests.cs.meta} | 2 +- .../InSceneObjectParentingTests.cs} | 4 +-- .../InSceneObjectParentingTests.cs.meta} | 0 .../InSceneObjectTests.cs} | 9 +++-- .../InSceneObjectTests.cs.meta} | 0 12 files changed, 69 insertions(+), 27 deletions(-) create mode 100644 testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject.meta rename testproject/Assets/Tests/Runtime/NetworkSceneManager/{InScenePlacedNetworkObjectBase.cs => InScenePlacedNetworkObject/InSceneObjectBase.cs} (94%) rename testproject/Assets/Tests/Runtime/NetworkSceneManager/{InScenePlacedNetworkObjectBase.cs.meta => InScenePlacedNetworkObject/InSceneObjectBase.cs.meta} (100%) rename testproject/Assets/Tests/Runtime/NetworkSceneManager/{InScenePlacedNetworkObjectDestroyTests.cs => InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs} (93%) rename testproject/Assets/Tests/Runtime/NetworkSceneManager/{InScenePlacedNetworkObjectDestroyTests.cs.meta => InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs.meta} (71%) rename testproject/Assets/Tests/Runtime/NetworkSceneManager/{InScenePlacedNetworkObjectParentingTests.cs => InScenePlacedNetworkObject/InSceneObjectParentingTests.cs} (97%) rename testproject/Assets/Tests/Runtime/NetworkSceneManager/{InScenePlacedNetworkObjectParentingTests.cs.meta => InScenePlacedNetworkObject/InSceneObjectParentingTests.cs.meta} (100%) rename testproject/Assets/Tests/Runtime/NetworkSceneManager/{InScenePlacedNetworkObjectTests.cs => InScenePlacedNetworkObject/InSceneObjectTests.cs} (94%) rename testproject/Assets/Tests/Runtime/NetworkSceneManager/{InScenePlacedNetworkObjectTests.cs.meta => InScenePlacedNetworkObject/InSceneObjectTests.cs.meta} (100%) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 26d0fad3d6..512e7ffdd7 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -1280,13 +1280,7 @@ public SceneEventProgressStatus UnloadScene(Scene scene) } } - sceneEventProgress.LoadSceneMode = LoadSceneMode.Additive; - - // Any NetworkObjects marked to not be destroyed with a scene and reside within the scene about to be unloaded - // should be migrated temporarily into the DDOL, once the scene is unloaded they will be migrated into the - // currently active scene. var networkManager = NetworkManager; - SceneManagerHandler.MoveObjectsFromSceneToDontDestroyOnLoad(ref networkManager, scene); var sceneEventData = BeginSceneEvent(); sceneEventData.SceneEventProgressId = sceneEventProgress.Guid; @@ -1297,10 +1291,16 @@ public SceneEventProgressStatus UnloadScene(Scene scene) // This will be the message we send to everyone when this scene event sceneEventProgress is complete sceneEventProgress.SceneEventType = SceneEventType.UnloadEventCompleted; + sceneEventProgress.LoadSceneMode = LoadSceneMode.Additive; sceneEventProgress.SceneEventId = sceneEventData.SceneEventId; sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded; + // Any NetworkObjects marked to not be destroyed with a scene and reside within the scene about to be unloaded + // should be migrated temporarily into the DDOL, once the scene is unloaded they will be migrated into the + // currently active scene. + SceneManagerHandler.MoveObjectsFromSceneToDontDestroyOnLoad(ref networkManager, scene); + if (!RemoveServerClientSceneHandle(sceneEventData.SceneHandle, scene.handle)) { Debug.LogError($"Failed to remove {SceneNameFromHash(sceneEventData.SceneHash)} scene handles [Server ({sceneEventData.SceneHandle})][Local({scene.handle})]"); @@ -2831,6 +2831,27 @@ internal bool IsSceneEventInProgress() return false; } + internal bool IsSceneUnloading(NetworkObject networkObject) + { + if (!NetworkManager.NetworkConfig.EnableSceneManagement) + { + return false; + } + + foreach (var sceneEventEntry in SceneEventProgressTracking) + { + var name = SceneNameFromHash(sceneEventEntry.Value.SceneHash); + var scene = SceneManager.GetSceneByName(name); + var sameScene = name == networkObject.gameObject.scene.name && scene.handle == networkObject.SceneOriginHandle; + + if (!sceneEventEntry.Value.HasTimedOut() && sceneEventEntry.Value.IsUnloading() && sceneEventEntry.Value.Status == SceneEventProgressStatus.Started && sameScene) + { + return true; + } + } + return false; + } + /// /// Handles notifying clients when a NetworkObject has been migrated into a new scene /// diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs index ebbb7b1bb9..a649aec4bb 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventProgress.cs @@ -223,6 +223,14 @@ internal void ClientFinishedSceneEvent(ulong clientId) } } + /// + /// Returns whether the SceneEventType is related to an unloading event. + /// + internal bool IsUnloading() + { + return SceneEventType is SceneEventType.Unload or SceneEventType.UnloadComplete or SceneEventType.UnloadEventCompleted; + } + /// /// Determines if the scene event has finished for both /// client(s) and server. diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index e706c56d4d..966326767a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1607,25 +1607,20 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec // We have to do this check first as subsequent checks assume we can access NetworkObjectId. if (!networkObject) { - Debug.LogWarning("Trying to destroy network object but it is null"); + NetworkLog.LogWarning("Trying to destroy network object but it is null"); return; } // Removal of spawned object if (!SpawnedObjects.ContainsKey(networkObject.NetworkObjectId)) { - if (!NetworkManager.ShutdownInProgress) + if (!NetworkManager.ShutdownInProgress && !NetworkManager.SceneManager.IsSceneEventInProgress()) { - Debug.LogWarning($"Trying to destroy object {networkObject.NetworkObjectId} but it doesn't seem to exist anymore!"); + NetworkLog.LogWarning($"Trying to destroy object {networkObject.NetworkObjectId} but it doesn't seem to exist anymore!"); } return; } - if (destroyGameObject && networkObject.IsSceneObject == true && NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - Debug.LogWarning("Destroying in-scene network objects can lead to unexpected behavior. It is recommended to use NetworkObject.Despawn(false) instead."); - } - var distributedAuthority = NetworkManager.DistributedAuthorityMode; var hasDAAuthority = distributedAuthority && (networkObject.HasAuthority || (NetworkManager.DAHost && authorityOverride)); var hasClientServerAuthority = !distributedAuthority && NetworkManager.IsServer; @@ -1635,6 +1630,11 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec // and only attempt to remove the child's parent on the server-side if (!NetworkManager.ShutdownInProgress && hasAuthority) { + if (destroyGameObject && networkObject.IsSceneObject == true && !NetworkManager.SceneManager.IsSceneUnloading(networkObject)) + { + NetworkLog.LogWarning("Destroying in-scene network objects can lead to unexpected behavior. It is recommended to use NetworkObject.Despawn(false) instead."); + } + // Get all child NetworkObjects var objectsToRemoveParent = networkObject.GetComponentsInChildren(); diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject.meta new file mode 100644 index 0000000000..2b58bd86d3 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1b544b65f3a74c1290a3599242a352d3 +timeCreated: 1750884425 \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectBase.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectBase.cs similarity index 94% rename from testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectBase.cs rename to testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectBase.cs index 1bbe4d92ca..a653a366aa 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectBase.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectBase.cs @@ -6,7 +6,7 @@ namespace TestProject.RuntimeTests { - public class InScenePlacedNetworkObjectBase : IntegrationTestWithApproximation + public class InSceneObjectBase : IntegrationTestWithApproximation { protected override int NumberOfClients => 2; @@ -14,10 +14,10 @@ public class InScenePlacedNetworkObjectBase : IntegrationTestWithApproximation private Scene m_AuthoritySideSceneLoaded; private Scene m_PreviousSceneLoaded; - protected InScenePlacedNetworkObjectBase(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } + protected InSceneObjectBase(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } // Constructor that is used by InScenePlacedNetworkObjectDestroyTests - protected InScenePlacedNetworkObjectBase(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } + protected InSceneObjectBase(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } protected override IEnumerator OnSetup() { @@ -36,6 +36,7 @@ protected override IEnumerator OnSetup() protected override IEnumerator OnTearDown() { GetAuthorityNetworkManager().SceneManager.OnSceneEvent -= OnSceneEvent; + NetworkObjectTestComponent.Reset(); yield return CleanUpLoadedScene(); } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectBase.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectBase.cs.meta similarity index 100% rename from testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectBase.cs.meta rename to testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectBase.cs.meta diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs similarity index 93% rename from testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs rename to testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs index 6648a2605c..489452e6c9 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs @@ -15,13 +15,13 @@ namespace TestProject.RuntimeTests [TestFixture(NetworkTopologyTypes.DistributedAuthority, DespawnMode.Despawn)] [TestFixture(NetworkTopologyTypes.DistributedAuthority, DespawnMode.DeferDespawn)] [TestFixture(NetworkTopologyTypes.ClientServer, DespawnMode.Despawn)] - public class InScenePlacedNetworkObjectDestroyTests : InScenePlacedNetworkObjectBase + public class InSceneObjectDestroyTests : InSceneObjectBase { protected override int NumberOfClients => 2; private readonly DespawnMode m_DespawnMode; - public InScenePlacedNetworkObjectDestroyTests(NetworkTopologyTypes networkTopologyType, DespawnMode despawnMode) : base(networkTopologyType) + public InSceneObjectDestroyTests(NetworkTopologyTypes networkTopologyType, DespawnMode despawnMode) : base(networkTopologyType) { m_DespawnMode = despawnMode; } @@ -95,6 +95,16 @@ private IEnumerator LoadSceneAndDespawnObject(DestroyMode destroyMode) serverObject.DeferDespawn(1, destroyGameObject); } + const string expectedLog = "[Netcode] Destroying in-scene network objects can lead to unexpected behavior. It is recommended to use NetworkObject.Despawn(false) instead."; + if (destroyGameObject) + { + NetcodeLogAssert.LogWasReceived(LogType.Warning, expectedLog); + } + else + { + NetcodeLogAssert.LogWasNotReceived(LogType.Warning, expectedLog); + } + yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == 0); AssertOnTimeout($"Timed out waiting for all in-scene instances to be despawned! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()}"); diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs.meta similarity index 71% rename from testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs.meta rename to testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs.meta index e07ec78d5c..3c0172efbe 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectDestroyTests.cs.meta +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs.meta @@ -1,3 +1,3 @@ fileFormatVersion: 2 guid: 0e794418a54341c391d527b5572b39b6 -timeCreated: 1750262429 \ No newline at end of file +timeCreated: 1750262429 diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectParentingTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectParentingTests.cs similarity index 97% rename from testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectParentingTests.cs rename to testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectParentingTests.cs index 2fffe07156..23363ee509 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectParentingTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectParentingTests.cs @@ -12,14 +12,14 @@ namespace TestProject.RuntimeTests [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)] [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)] [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Server)] - public class InScenePlacedNetworkObjectParentingTests : InScenePlacedNetworkObjectBase + public class InSceneObjectParentingTests : InSceneObjectBase { protected override int NumberOfClients => 2; private const string k_InSceneUnder = "InSceneUnderGameObject"; private const string k_InSceneUnderWithNetworkTransform = "InSceneUnderGameObjectWithNT"; - public InScenePlacedNetworkObjectParentingTests(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } + public InSceneObjectParentingTests(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } [UnityTest] public IEnumerator ParentedInSceneObjectLateJoiningClient() diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectParentingTests.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectParentingTests.cs.meta similarity index 100% rename from testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectParentingTests.cs.meta rename to testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectParentingTests.cs.meta diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectTests.cs similarity index 94% rename from testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs rename to testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectTests.cs index 88d3504552..ba384e7800 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectTests.cs @@ -13,11 +13,11 @@ namespace TestProject.RuntimeTests [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)] [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)] [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Server)] - public class InScenePlacedNetworkObjectTests : InScenePlacedNetworkObjectBase + public class InSceneObjectTests : InSceneObjectBase { protected override int NumberOfClients => 2; - public InScenePlacedNetworkObjectTests(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } + public InSceneObjectTests(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } private int m_NumberOfInstancesCheck; @@ -35,7 +35,6 @@ public InScenePlacedNetworkObjectTests(NetworkTopologyTypes networkTopologyType, [UnityTest] public IEnumerator EnableDisableInSceneObjectTests() { - m_EnableVerboseDebug = true; NetworkObjectTestComponent.ServerNetworkObjectInstance = null; // Enabled disabling the NetworkObject when it is despawned NetworkObjectTestComponent.DisableOnDespawn = true; @@ -115,7 +114,7 @@ public IEnumerator EnableDisableInSceneObjectTests() [TestFixture(NetworkTopologyTypes.DistributedAuthority)] [TestFixture(NetworkTopologyTypes.ClientServer)] - internal class InScenePlacedNetworkObjectClientTests : NetcodeIntegrationTest + internal class InSceneObjectClientTests : NetcodeIntegrationTest { private const string k_SceneToLoad = "InSceneNetworkObject"; @@ -123,7 +122,7 @@ internal class InScenePlacedNetworkObjectClientTests : NetcodeIntegrationTest private Scene m_Scene; - public InScenePlacedNetworkObjectClientTests(NetworkTopologyTypes topology) : base(topology) + public InSceneObjectClientTests(NetworkTopologyTypes topology) : base(topology) { } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectTests.cs.meta similarity index 100% rename from testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs.meta rename to testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectTests.cs.meta From 3ce47d24ddd22384d805286c2f7275ff8d2948b6 Mon Sep 17 00:00:00 2001 From: Emma Date: Tue, 8 Jul 2025 13:49:50 -0400 Subject: [PATCH 07/10] Ensure all clients can re-parent objects in DA mode --- .../Runtime/Spawning/NetworkSpawnManager.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 966326767a..92b90972a7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1622,13 +1622,11 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } var distributedAuthority = NetworkManager.DistributedAuthorityMode; - var hasDAAuthority = distributedAuthority && (networkObject.HasAuthority || (NetworkManager.DAHost && authorityOverride)); - var hasClientServerAuthority = !distributedAuthority && NetworkManager.IsServer; - var hasAuthority = hasDAAuthority || hasClientServerAuthority; // If we are shutting down the NetworkManager, then ignore resetting the parent - // and only attempt to remove the child's parent on the server-side - if (!NetworkManager.ShutdownInProgress && hasAuthority) + // Remove the child's parent server-side or in distributedAuthorityMode + // DistributedAuthorityMode: All clients need to remove the parent locally due to mixed-authority hierarchies and race-conditions + if (!NetworkManager.ShutdownInProgress && (NetworkManager.IsServer || distributedAuthority)) { if (destroyGameObject && networkObject.IsSceneObject == true && !NetworkManager.SceneManager.IsSceneUnloading(networkObject)) { @@ -1654,7 +1652,7 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } // For mixed authority hierarchies, if the parent is despawned then any removal of children // is considered "authority approved". Set the AuthorityAppliedParenting flag. - spawnedNetObj.AuthorityAppliedParenting = authorityOverride; + spawnedNetObj.AuthorityAppliedParenting = distributedAuthority && !networkObject.HasAuthority; // Try to remove the parent using the cached WorldPositionStays value // Note: WorldPositionStays will still default to true if this was an @@ -1677,7 +1675,12 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec networkObject.InvokeBehaviourNetworkDespawn(); - if (!NetworkManager.ShutdownInProgress && hasAuthority) + // Whether we are in distributedAuthority mode and have authority on this object + var hasDAAuthority = distributedAuthority && (networkObject.HasAuthority || (NetworkManager.DAHost && authorityOverride)); + + // Don't send messages if shutting down + // Otherwise send messages if we are the authority (either the server, or the DA mode authority of this object). + if (!NetworkManager.ShutdownInProgress && (hasDAAuthority || (!distributedAuthority && NetworkManager.IsServer))) { if (NetworkManager.NetworkConfig.RecycleNetworkIds) { @@ -1692,7 +1695,6 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec /* * Configure message targets */ - // If we are using distributed authority and are not the DAHost, send a message to the Server (CMBService or DAHost) if (hasDAAuthority && !NetworkManager.DAHost) { From ba1eb007cab5ba8e47ee59d88fe8fbe91a92785f Mon Sep 17 00:00:00 2001 From: Emma Date: Tue, 8 Jul 2025 17:42:20 -0400 Subject: [PATCH 08/10] Update NetworkShowHideTests to work with cmb service --- .../NetworkVariablePermission.cs | 4 ++ .../Tests/Runtime/NetworkShowHideTests.cs | 53 +++++++++++++------ 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariablePermission.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariablePermission.cs index a0679715ce..1ea52239dc 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariablePermission.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariablePermission.cs @@ -3,6 +3,10 @@ namespace Unity.Netcode /// /// The permission types for reading a var /// + /// + /// Only relevant when using the client/server network topology. + /// In distributed authority mode everyone can always read. + /// public enum NetworkVariableReadPermission { /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs index f0e01a842c..a12985579c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs @@ -113,9 +113,27 @@ public void SomeRandomClientRPC() ClientIdsRpcCalledOn?.Add(NetworkManager.LocalClientId); } + [Rpc(SendTo.Everyone)] + public void DistributedAuthorityRPC() + { + if (!Silent) + { + Debug.Log($"RPC called {NetworkManager.LocalClientId}"); + } + ClientIdsRpcCalledOn?.Add(NetworkManager.LocalClientId); + } + public void TriggerRpc() { - SomeRandomClientRPC(); + Debug.Log("triggering RPC"); + if (NetworkManager.CMBServiceConnection) + { + DistributedAuthorityRPC(); + } + else + { + SomeRandomClientRPC(); + } } } @@ -125,12 +143,6 @@ internal class NetworkShowHideTests : NetcodeIntegrationTest { protected override int NumberOfClients => 4; - // TODO: [CmbServiceTests] https://jira.unity3d.com/browse/MTTB-1392 - protected override bool UseCMBService() - { - return false; - } - private ulong m_ClientId0; private GameObject m_PrefabToSpawn; private GameObject m_PrefabSpawnWithoutObservers; @@ -580,11 +592,15 @@ public IEnumerator NetworkHideChangeOwnershipNotHidden() var firstClient = GetNonAuthorityNetworkManager(0); var secondClient = GetNonAuthorityNetworkManager(1); + ShowHideObject.ValueAfterOwnershipChange = -1; ShowHideObject.ClientTargetedNetworkObjects.Clear(); ShowHideObject.ObjectsPerClientId.Clear(); ShowHideObject.ClientIdToTarget = secondClient.LocalClientId; ShowHideObject.Silent = true; + // only check for value change on one specific client + ShowHideObject.NetworkManagerOfInterest = firstClient; + var spawnedObject1 = SpawnObject(m_PrefabToSpawn, authority); m_NetSpawnedObject1 = spawnedObject1.GetComponent(); @@ -597,11 +613,20 @@ public IEnumerator NetworkHideChangeOwnershipNotHidden() // wait for three ticks yield return WaitForTicks(authority, 3); + if (!m_DistributedAuthority) + { + // Client/Server ClientIdToTarget should not see any value change + Assert.That(ShowHideObject.ValueAfterOwnershipChange, Is.EqualTo(-1)); + } + else + { + // Distributed Authority mode everyone can always read so the value change should already have happened + Assert.That(ShowHideObject.ValueAfterOwnershipChange, Is.EqualTo(1)); + } + // check we'll actually be changing owners Assert.False(ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId == firstClient.LocalClientId); - // only check for value change on one specific client - ShowHideObject.NetworkManagerOfInterest = firstClient; // change ownership m_NetSpawnedObject1.ChangeOwnership(firstClient.LocalClientId); @@ -731,10 +756,8 @@ public IEnumerator NetworkShowHideAroundListModify() m_NetSpawnedObject1 = spawnedObject1.GetComponent(); // wait for host to have spawned and gained ownership - while (ShowHideObject.GainOwnershipCount == 0) - { - yield return new WaitForSeconds(0.0f); - } + yield return WaitForConditionOrTimeOut(() => m_NetSpawnedObject1.IsSpawned && m_NetSpawnedObject1.OwnerClientId == authority.LocalClientId); + AssertOnTimeout($"Timed out waiting for {m_NetSpawnedObject1.name} to spawn"); for (int i = 0; i < 4; i++) { @@ -767,8 +790,8 @@ public IEnumerator NetworkShowHideAroundListModify() } - Compare(ShowHideObject.ObjectsPerClientId[0].MyList, ShowHideObject.ObjectsPerClientId[1].MyList); - Compare(ShowHideObject.ObjectsPerClientId[0].MyList, ShowHideObject.ObjectsPerClientId[2].MyList); + Compare(ShowHideObject.ObjectsPerClientId[authority.LocalClientId].MyList, ShowHideObject.ObjectsPerClientId[firstClient.LocalClientId].MyList); + Compare(ShowHideObject.ObjectsPerClientId[authority.LocalClientId].MyList, ShowHideObject.ObjectsPerClientId[secondClient.LocalClientId].MyList); } } From 0256f05c6a3c44c467d1a65e0d4c061dde163eea Mon Sep 17 00:00:00 2001 From: Emma Date: Tue, 8 Jul 2025 14:32:14 -0400 Subject: [PATCH 09/10] rename test network objects --- .../NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs index cf4f83d17f..f243918d2d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs @@ -37,10 +37,10 @@ public NetworkObjectDontDestroyWithOwnerTests(HostOrServer hostOrServer) : base( protected override void OnServerAndClientsCreated() { - m_DontDestroyWithOwnerPrefab = CreateNetworkObjectPrefab("DestroyWith"); + m_DontDestroyWithOwnerPrefab = CreateNetworkObjectPrefab("DontDestroyWith"); m_DontDestroyWithOwnerPrefab.GetComponent().DontDestroyWithOwner = true; - m_DestroyWithOwnerPrefab = CreateNetworkObjectPrefab("DontDestroyWith"); + m_DestroyWithOwnerPrefab = CreateNetworkObjectPrefab("DestroyWith"); m_PrefabNoObserversSpawn = CreateNetworkObjectPrefab("NoObserversObject"); var prefabNoObserversNetworkObject = m_PrefabNoObserversSpawn.GetComponent(); From 1113909ac493a3167dbdc3a7e060d159ae352544 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 10 Jul 2025 12:46:48 -0400 Subject: [PATCH 10/10] Add missing import --- .../InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs index 489452e6c9..9a01ceb5c1 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs @@ -2,6 +2,7 @@ using System.Linq; using NUnit.Framework; using Unity.Netcode; +using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TestTools;