diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9788b12a69..fe8f2d6d58 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -16,6 +16,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 diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index f55997b3e7..96f0223ef3 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) @@ -1179,18 +1159,13 @@ internal void OnClientDisconnectFromServer(ulong clientId) // If destroying with owner, then always despawn and destroy (or defer destroying to prefab handler) if (!ownedObject.DontDestroyWithOwner) { - 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..f37c9f95a4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -6,17 +6,13 @@ 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 +21,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) @@ -74,6 +67,10 @@ public void Serialize(FastBufferWriter writer, int targetVersion) BytePacker.WriteValueBitPacked(writer, DeferredDespawnTick); } } + else if (targetVersion >= k_AllowDestroyGameInPlaced) + { + writer.WriteByteSafe(m_DestroyFlags); + } if (targetVersion < k_OptimizeDestroyObjectMessage) { @@ -103,10 +100,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 +171,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 +210,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/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/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 8a39fcfa06..92b90972a7 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,25 +1607,32 @@ 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 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 we are shutting down the NetworkManager, then ignore resetting the parent + // 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)) + { + 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(); @@ -1630,9 +1651,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 = distributedAuthority && !networkObject.HasAuthority; // Try to remove the parent using the cached WorldPositionStays value // Note: WorldPositionStays will still default to true if this was an @@ -1655,9 +1675,12 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec networkObject.InvokeBehaviourNetworkDespawn(); - if (NetworkManager != null && ((NetworkManager.IsServer && (!distributedAuthority || - (distributedAuthority && modeDestroy))) || - (distributedAuthority && networkObject.OwnerClientId == NetworkManager.LocalClientId))) + // 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) { @@ -1669,14 +1692,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) + { + m_TargetClientIds.Add(NetworkManager.ServerClientId); + } + // Otherwise send to the clients for which the object is visible + else { - // We keep only the client for which the object is visible - // as the other clients have them already despawned foreach (var clientId in NetworkManager.ConnectedClientsIds) { if ((distributedAuthority && clientId == networkObject.OwnerClientId) || clientId == NetworkManager.LocalClientId) @@ -1689,17 +1715,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 +2104,7 @@ internal struct DeferredDespawnObject { public int TickToDespawn; public bool HasDeferredDespawnCheck; + public bool DestroyGameObject; public ulong NetworkObjectId; } @@ -2097,12 +2116,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 +2189,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/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(); 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..a12985579c 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(); } @@ -120,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(); + } } } @@ -132,12 +143,6 @@ internal class NetworkShowHideTests : NetcodeIntegrationTest { protected override int NumberOfClients => 4; - // TODO: [CmbServiceTests] Adapt to run with the service - protected override bool UseCMBService() - { - return false; - } - private ulong m_ClientId0; private GameObject m_PrefabToSpawn; private GameObject m_PrefabSpawnWithoutObservers; @@ -166,7 +171,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 +196,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 +254,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 +265,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 +291,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 +313,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 +332,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 +368,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 +381,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 +398,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 +409,11 @@ public IEnumerator NetworkHideDespawnTest() private bool CheckListedClientsVisibility() { - if (m_ClientsWithVisibility.Contains(m_ServerNetworkManager.LocalClientId)) - { - if (!m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_ObserverTestObject.NetworkObjectId)) - { - return false; - } - } - - foreach (var client in m_ClientNetworkManagers) + foreach (var manager in m_NetworkManagers) { - if (m_ClientsWithVisibility.Contains(client.LocalClientId)) + if (m_ClientsWithVisibility.Contains(manager.LocalClientId)) { - if (!client.SpawnManager.SpawnedObjects.ContainsKey(m_ObserverTestObject.NetworkObjectId)) + if (!manager.SpawnManager.SpawnedObjects.ContainsKey(m_ObserverTestObject.NetworkObjectId)) { return false; } @@ -424,11 +425,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 +440,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 +451,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 +485,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 +509,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 +525,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 +535,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 +553,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 +588,20 @@ private bool AllClientsSpawnedObject1() [UnityTest] public IEnumerator NetworkHideChangeOwnershipNotHidden() { + var authority = GetAuthorityNetworkManager(); + var firstClient = GetNonAuthorityNetworkManager(0); + var secondClient = GetNonAuthorityNetworkManager(1); + + ShowHideObject.ValueAfterOwnershipChange = -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); + // only check for value change on one specific client + ShowHideObject.NetworkManagerOfInterest = firstClient; + + var spawnedObject1 = SpawnObject(m_PrefabToSpawn, authority); m_NetSpawnedObject1 = spawnedObject1.GetComponent(); yield return WaitForConditionOrTimeOut(AllClientsSpawnedObject1); @@ -581,23 +611,32 @@ public IEnumerator NetworkHideChangeOwnershipNotHidden() m_NetSpawnedObject1.GetComponent().MyOwnerReadNetworkVariable.Value++; // wait for three ticks - yield return WaitForTicks(m_ServerNetworkManager, 3); + 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 == 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]; // 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,111 +676,113 @@ 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 - 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++) { // 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})!"); @@ -749,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); } } 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/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/InScenePlacedNetworkObject/InSceneObjectBase.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectBase.cs new file mode 100644 index 0000000000..a653a366aa --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectBase.cs @@ -0,0 +1,185 @@ +using System.Collections; +using System.Linq; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine.SceneManagement; + +namespace TestProject.RuntimeTests +{ + public class InSceneObjectBase : IntegrationTestWithApproximation + { + protected override int NumberOfClients => 2; + + internal const string SceneToLoad = "InSceneNetworkObject"; + private Scene m_AuthoritySideSceneLoaded; + private Scene m_PreviousSceneLoaded; + + protected InSceneObjectBase(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } + + // Constructor that is used by InScenePlacedNetworkObjectDestroyTests + protected InSceneObjectBase(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; + NetworkObjectTestComponent.Reset(); + 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/InScenePlacedNetworkObject/InSceneObjectBase.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectBase.cs.meta new file mode 100644 index 0000000000..65af909c98 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectBase.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/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs new file mode 100644 index 0000000000..9a01ceb5c1 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs @@ -0,0 +1,186 @@ +using System.Collections; +using System.Linq; +using NUnit.Framework; +using Unity.Netcode; +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 InSceneObjectDestroyTests : InSceneObjectBase + { + protected override int NumberOfClients => 2; + + private readonly DespawnMode m_DespawnMode; + + public InSceneObjectDestroyTests(NetworkTopologyTypes networkTopologyType, DespawnMode despawnMode) : base(networkTopologyType) + { + m_DespawnMode = despawnMode; + } + + public enum DespawnMode + { + Despawn, + DeferDespawn, + } + + private enum DestroyMode + { + DestroyGameObject, + DespawnGameObject, + } + + private NetworkObject m_JoinedClientDespawnedNetworkObject; + private void OnInSceneObjectDespawned(NetworkObject networkObject) + { + m_JoinedClientDespawnedNetworkObject = networkObject; + NetworkObjectTestComponent.OnInSceneObjectDespawned -= OnInSceneObjectDespawned; + } + + /// + /// 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; + + VerboseDebug("Loading scene"); + 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(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(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!"); + 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. destroyGameObject: {destroyGameObject}"); + // Despawn the in-scene placed NetworkObject + if (m_DespawnMode == DespawnMode.Despawn) + { + serverObject.Despawn(destroyGameObject); + } + else + { + 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()}"); + + foreach (var manager in m_NetworkManagers) + { + Assert.False(manager.SpawnManager.SpawnedObjects.ContainsKey(serverObjectId), $"Client-{manager.LocalClientId} still has in-scene instance spawned!"); + } + + 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.DespawnGameObject); + + 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(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"); + + // 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(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}"); + } + } +} diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs.meta new file mode 100644 index 0000000000..3c0172efbe --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectDestroyTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0e794418a54341c391d527b5572b39b6 +timeCreated: 1750262429 diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectParentingTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectParentingTests.cs new file mode 100644 index 0000000000..23363ee509 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectParentingTests.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 InSceneObjectParentingTests : InSceneObjectBase + { + protected override int NumberOfClients => 2; + + private const string k_InSceneUnder = "InSceneUnderGameObject"; + private const string k_InSceneUnderWithNetworkTransform = "InSceneUnderGameObjectWithNT"; + + public InSceneObjectParentingTests(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/InScenePlacedNetworkObject/InSceneObjectParentingTests.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectParentingTests.cs.meta new file mode 100644 index 0000000000..3b5b0cfa11 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectParentingTests.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/InScenePlacedNetworkObject/InSceneObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectTests.cs new file mode 100644 index 0000000000..ba384e7800 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InSceneObjectTests.cs @@ -0,0 +1,167 @@ +using System.Collections; +using NUnit.Framework; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +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 InSceneObjectTests : InSceneObjectBase + { + protected override int NumberOfClients => 2; + + public InSceneObjectTests(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } + + + private int m_NumberOfInstancesCheck; + + + /// + /// This validates that users can despawn in-scene placed NetworkObjects and disable the + /// associated GameObject when OnNetworkDespawn is invoked while still being able to + /// re-spawn the same in-scene placed NetworkObject. + /// This test validates this for: + /// - Currently connected clients + /// - Late joining client + /// - Scene switching and having the server despawn the NetworkObject the first time it is spawned. + /// + [UnityTest] + public IEnumerator EnableDisableInSceneObjectTests() + { + NetworkObjectTestComponent.ServerNetworkObjectInstance = null; + // Enabled disabling the NetworkObject when it is despawned + NetworkObjectTestComponent.DisableOnDespawn = true; + var authority = GetAuthorityNetworkManager(); + + 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); + AssertOnTimeout($"Timed out waiting for all instances to be spawned and enabled!"); + + var serverInSceneObjectInstance = NetworkObjectTestComponent.ServerNetworkObjectInstance; + Assert.IsNotNull(serverInSceneObjectInstance, $"Could not get the server-side registration of {nameof(NetworkObjectTestComponent)}!"); + + // Test #1: Despawn the in-scene placed NetworkObject and verify it is despawned and disabled on the clients + serverInSceneObjectInstance.Despawn(false); + + yield return WaitForConditionOrTimeOut(HaveAllClientsDespawnedInSceneObject); + 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 + var newlyJoinedClient = CreateNewClient(); + yield return StartClient(newlyJoinedClient); + + yield return WaitForConditionOrTimeOut(HaveAllClientsDespawnedInSceneObject); + AssertOnTimeout($"[Test #2] Timed out waiting for all instances to be despawned and disabled!"); + + // Test #3: Now spawn the same in-scene placed NetworkObject + serverInSceneObjectInstance.gameObject.SetActive(true); + serverInSceneObjectInstance.Spawn(); + yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedInSceneObject); + + // 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 + // connected clients completed the scene switch and that all in-scene placed NetworkObjects + // are despawned and disabled. + + NetworkObjectTestComponent.ServerNetworkObjectInstance = null; + NetworkObjectTestComponent.DisableOnSpawn = true; + + 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!"); + + // Go ahead and clear out the despawned instances list + NetworkObjectTestComponent.DespawnedInstances.Clear(); + + // Now scene switch (LoadSceneMode.Single) into the scene with the in-scene placed NetworkObject we have been testing + 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); + AssertOnTimeout($"[Test #4] Timed out waiting for all instances to be despawned and disabled!"); + + serverInSceneObjectInstance = NetworkObjectTestComponent.ServerNetworkObjectInstance; + Assert.IsNotNull(serverInSceneObjectInstance, $"[Test #4] Could not get the server-side registration of {nameof(NetworkObjectTestComponent)}!"); + + // Test #5: Now spawn the in-scene placed NetworkObject + serverInSceneObjectInstance.gameObject.SetActive(true); + serverInSceneObjectInstance.Spawn(); + + // 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!"); + } + } + + [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer)] + internal class InSceneObjectClientTests : NetcodeIntegrationTest + { + private const string k_SceneToLoad = "InSceneNetworkObject"; + + protected override int NumberOfClients => 0; + + private Scene m_Scene; + + public InSceneObjectClientTests(NetworkTopologyTypes topology) : base(topology) + { + } + + protected override IEnumerator OnSetup() + { + SceneManager.sceneLoaded += SceneManager_sceneLoaded; + SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Additive); + return base.OnSetup(); + } + + private void SceneManager_sceneLoaded(Scene scene, LoadSceneMode loadSceneMode) + { + if (scene.name == k_SceneToLoad && loadSceneMode == LoadSceneMode.Additive) + { + m_Scene = scene; + SceneManager.sceneLoaded -= SceneManager_sceneLoaded; + } + } + + protected override IEnumerator OnTearDown() + { + if (m_Scene.isLoaded) + { + SceneManager.UnloadSceneAsync(m_Scene); + } + return base.OnTearDown(); + } + + [UnityTest] + public IEnumerator DespawnAndDestroyNetworkObjects() + { + // Simulate a client disconnecting early by just invoking DespawnAndDestroyNetworkObjects to assure + // this method does not destroy in-scene placed NetworkObjects. + GetAuthorityNetworkManager().SpawnManager.DespawnAndDestroyNetworkObjects(); + + yield return s_DefaultWaitForTick; + + 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/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 diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs deleted file mode 100644 index 54cb4c7be4..0000000000 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObjectTests.cs +++ /dev/null @@ -1,639 +0,0 @@ -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; -using UnityEngine.TestTools; - - - -namespace TestProject.RuntimeTests -{ - [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)] - [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)] - [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Server)] - public class InScenePlacedNetworkObjectTests : IntegrationTestWithApproximation - { - 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); - } - - 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] - 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.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 - /// associated GameObject when OnNetworkDespawn is invoked while still being able to - /// re-spawn the same in-scene placed NetworkObject. - /// This test validates this for: - /// - Currently connected clients - /// - Late joining client - /// - Scene switching and having the server despawn the NetworkObject the first time it is spawned. - /// - [UnityTest] - public IEnumerator EnableDisableInSceneObjectTests() - { - 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; - - // Verify all connected clients spawned the in-scene placed NetworkObject - yield return WaitForConditionOrTimeOut(HaveAllClientsSpawnedInSceneObject); - AssertOnTimeout($"Timed out waiting for all instances to be spawned and enabled!"); - - var serverInSceneObjectInstance = NetworkObjectTestComponent.ServerNetworkObjectInstance; - Assert.IsNotNull(serverInSceneObjectInstance, $"Could not get the server-side registration of {nameof(NetworkObjectTestComponent)}!"); - - // Test #1: Despawn the in-scene placed NetworkObject and verify it is despawned and disabled on the clients - serverInSceneObjectInstance.Despawn(false); - - yield return WaitForConditionOrTimeOut(HaveAllClientsDespawnedInSceneObject); - 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 = m_LateJoinClient; - - m_NumberOfInstancesCheck++; - yield return WaitForConditionOrTimeOut(HaveAllClientsDespawnedInSceneObject); - AssertOnTimeout($"[Test #2] Timed out waiting for all instances to be despawned and disabled!"); - - // Test #3: Now spawn the same in-scene placed NetworkObject - 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 - // 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; - - // 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!"); - - // Go ahead and clear out the despawned instances list - 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; - - // Verify all client instances are disabled and despawned when done scene switching - yield return WaitForConditionOrTimeOut(HaveAllClientsDespawnedInSceneObject); - AssertOnTimeout($"[Test #4] Timed out waiting for all instances to be despawned and disabled!"); - - serverInSceneObjectInstance = NetworkObjectTestComponent.ServerNetworkObjectInstance; - Assert.IsNotNull(serverInSceneObjectInstance, $"[Test #4] Could not get the server-side registration of {nameof(NetworkObjectTestComponent)}!"); - - // Test #5: Now spawn the in-scene placed NetworkObject - serverInSceneObjectInstance.gameObject.SetActive(true); - serverInSceneObjectInstance.Spawn(); - - // 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 - } - - 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; - } - - } - - internal class InScenePlacedNetworkObjectClientTests : NetcodeIntegrationTest - { - private const string k_SceneToLoad = "InSceneNetworkObject"; - - protected override int NumberOfClients => 0; - - private Scene m_Scene; - - - // TODO: [CmbServiceTests] Adapt to run with the service - protected override bool UseCMBService() - { - return false; - } - - protected override IEnumerator OnSetup() - { - SceneManager.sceneLoaded += SceneManager_sceneLoaded; - SceneManager.LoadScene(k_SceneToLoad, LoadSceneMode.Additive); - return base.OnSetup(); - } - - private void SceneManager_sceneLoaded(Scene scene, LoadSceneMode loadSceneMode) - { - if (scene.name == k_SceneToLoad && loadSceneMode == LoadSceneMode.Additive) - { - m_Scene = scene; - SceneManager.sceneLoaded -= SceneManager_sceneLoaded; - } - } - - protected override IEnumerator OnTearDown() - { - if (m_Scene.isLoaded) - { - SceneManager.UnloadSceneAsync(m_Scene); - } - return base.OnTearDown(); - } - - [UnityTest] - public IEnumerator DespawnAndDestroyNetworkObjects() - { - // Simulate a client disconnecting early by just invoking DespawnAndDestroyNetworkObjects to assure - // this method does not destroy in-scene placed NetworkObjects. - GetAuthorityNetworkManager().SpawnManager.DespawnAndDestroyNetworkObjects(); - - yield return s_DefaultWaitForTick; - - 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}!");